Ответ Барри дает рабочее решение вопроса, поставленного оригинальным постером. Спасибо им обоим за вопросы и ответы.
Я нашел этот поток, когда пытался найти решение довольно похожей проблемы: программно создать дерево выражений, которое включает вызов метода Any (). Однако в качестве дополнительного ограничения конечной целью моего решения было передать такое динамически создаваемое выражение через Linq-to-SQL, чтобы работа вычисления Any () фактически выполнялась в БД. сам.
К сожалению, решение, обсуждавшееся до сих пор, не является чем-то, что может обработать Linq-to-SQL.
Исходя из предположения, что это может быть довольно популярной причиной для создания динамического дерева выражений, я решил дополнить поток своими выводами.
Когда я попытался использовать результат 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 (), чтобы она принимала предикатное выражение, а не делегат для логики предиката 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, кажется, в порядке с этим, поэтому я оставил его на месте (хотя это было ненужным). Я не был полностью уверен, что это преобразование может понадобиться при более надежном использовании, чем мой игрушечный образец.