Ответ Барри дает рабочее решение вопроса, поставленного на оригинальном плакате. Спасибо обоим этим людям за вопросы и ответы.
Я нашел этот поток, когда пытался придумать решение очень похожей проблемы: программное создание дерева выражений, которое включает вызов метода Any (). Однако в качестве дополнительного ограничения конечной целью моего решения было передать такое динамически созданное выражение через Linq-to-SQL, чтобы работа оценки Any () фактически выполнялась в Сама БД.
К сожалению, решение, которое обсуждалось до сих пор, не подходит для Linq-to-SQL.
Исходя из предположения, что это может быть довольно популярной причиной для построения динамического дерева выражений, я решил дополнить поток своими выводами.
Когда я попытался использовать результат Barry CallAny () в качестве выражения в предложении Linq-to-SQL Where (), я получил InvalidOperationException со следующими свойствами:
- HResult = -2146233079
- Message = "Внутренняя ошибка поставщика данных .NET Framework 1025"
- Источник = System.Data.Entity
После сравнения жестко запрограммированного дерева выражений с динамически созданным с помощью CallAny () я обнаружил, что основная проблема связана с Compile () выражения предиката и попыткой вызвать полученный делегат в CallAny (). Не углубляясь в детали реализации Linq-to-SQL, мне казалось разумным, что Linq-to-SQL не знает, что делать с такой структурой.
Поэтому после некоторых экспериментов я смог достичь желаемой цели, немного изменив предложенную реализацию CallAny (), чтобы она принимала выражение predicateExpression, а не делегат для логики предиката Any ().
Мой исправленный метод:
static Expression CallAny(Expression collection, Expression predicateExpression)
{
Type cType = GetIEnumerableImpl(collection.Type);
collection = Expression.Convert(collection, cType); // (see "NOTE" below)
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool));
// Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
MethodInfo anyMethod = (MethodInfo)
GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType },
new[] { cType, predType }, BindingFlags.Static);
return Expression.Call(
anyMethod,
collection,
predicateExpression);
}
Теперь я продемонстрирую его использование с EF. Для ясности я должен сначала показать модель предметной области и контекст EF, который я использую. По сути, моя модель - это упрощенный домен блогов и сообщений ... где в блоге есть несколько сообщений, и у каждого сообщения есть дата:
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
Когда этот домен установлен, вот мой код, который в конечном итоге использует исправленный CallAny () и заставляет Linq-to-SQL выполнять работу по оценке Any (). Мой конкретный пример будет сфокусирован на возврате всех блогов, у которых есть хотя бы одно сообщение, которое новее, чем указанная дата окончания.
static void Main()
{
Database.SetInitializer<BloggingContext>(
new DropCreateDatabaseAlways<BloggingContext>());
using (var ctx = new BloggingContext())
{
// insert some data
var blog = new Blog(){Name = "blog"};
blog.Posts = new List<Post>()
{ new Post() { Title = "p1", Date = DateTime.Parse("01/01/2001") } };
blog.Posts = new List<Post>()
{ new Post() { Title = "p2", Date = DateTime.Parse("01/01/2002") } };
blog.Posts = new List<Post>()
{ new Post() { Title = "p3", Date = DateTime.Parse("01/01/2003") } };
ctx.Blogs.Add(blog);
blog = new Blog() { Name = "blog 2" };
blog.Posts = new List<Post>()
{ new Post() { Title = "p1", Date = DateTime.Parse("01/01/2001") } };
ctx.Blogs.Add(blog);
ctx.SaveChanges();
// first, do a hard-coded Where() with Any(), to demonstrate that
// Linq-to-SQL can handle it
var cutoffDateTime = DateTime.Parse("12/31/2001");
var hardCodedResult =
ctx.Blogs.Where((b) => b.Posts.Any((p) => p.Date > cutoffDateTime));
var hardCodedResultCount = hardCodedResult.ToList().Count;
Debug.Assert(hardCodedResultCount > 0);
// now do a logically equivalent Where() with Any(), but programmatically
// build the expression tree
var blogsWithRecentPostsExpression =
BuildExpressionForBlogsWithRecentPosts(cutoffDateTime);
var dynamicExpressionResult =
ctx.Blogs.Where(blogsWithRecentPostsExpression);
var dynamicExpressionResultCount = dynamicExpressionResult.ToList().Count;
Debug.Assert(dynamicExpressionResultCount > 0);
Debug.Assert(dynamicExpressionResultCount == hardCodedResultCount);
}
}
Где BuildExpressionForBlogsWithRecentPosts () - это вспомогательная функция, которая использует CallAny () следующим образом:
private Expression<Func<Blog, Boolean>> BuildExpressionForBlogsWithRecentPosts(
DateTime cutoffDateTime)
{
var blogParam = Expression.Parameter(typeof(Blog), "b");
var postParam = Expression.Parameter(typeof(Post), "p");
// (p) => p.Date > cutoffDateTime
var left = Expression.Property(postParam, "Date");
var right = Expression.Constant(cutoffDateTime);
var dateGreaterThanCutoffExpression = Expression.GreaterThan(left, right);
var lambdaForTheAnyCallPredicate =
Expression.Lambda<Func<Post, Boolean>>(dateGreaterThanCutoffExpression,
postParam);
// (b) => b.Posts.Any((p) => p.Date > cutoffDateTime))
var collectionProperty = Expression.Property(blogParam, "Posts");
var resultExpression = CallAny(collectionProperty, lambdaForTheAnyCallPredicate);
return Expression.Lambda<Func<Blog, Boolean>>(resultExpression, blogParam);
}
ПРИМЕЧАНИЕ. Я обнаружил еще одну, казалось бы, неважную разницу между жестко запрограммированными и динамически построенными выражениями. У динамически созданной версии есть «дополнительный» вызов convert, которого в жестко запрограммированной версии, похоже, нет (или она не нужна?). Преобразование вводится в реализации CallAny (). Linq-to-SQL, похоже, не против, поэтому я оставил его на месте (хотя в этом не было необходимости). Я не был полностью уверен, может ли это преобразование понадобиться для более надежного использования, чем мой игрушечный образец.
person
Aaron Heusser
schedule
08.08.2013