Barry的回答提供了一个解决原帖中提出的问题的可行方法。感谢这两个人的提问和回答。
我在尝试解决一个非常相似的问题时发现了这个线程:编程创建一个包含调用Any()方法的表达式树。然而,我的解决方案的最终目标是将这样一个动态创建的表达式通过Linq-to-SQL传递,以便Any()评估的工作实际上是在数据库本身中执行的。
不幸的是,到目前为止讨论的解决方案并不是Linq-to-SQL可以处理的东西。
基于这可能是想要构建动态表达式树的一个相当普遍的原因的假设,我决定用我的发现来增强这个线程。
当我尝试将Barry的CallAny()的结果用作Linq-to-SQL Where()子句中的表达式时,我收到了InvalidOperationException,并显示以下属性:
- HResult=-2146233079
- Message="Internal .NET Framework Data Provider error 1025"
- Source=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);
Type elemType = cType.GetGenericArguments()[0];
Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(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())
{
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();
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);
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");
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);
var collectionProperty = Expression.Property(blogParam, "Posts");
var resultExpression = CallAny(collectionProperty, lambdaForTheAnyCallPredicate);
return Expression.Lambda<Func<Blog, Boolean>>(resultExpression, blogParam);
}
注意:我发现硬编码和动态构建表达式之间存在另一个看似不重要的差异。动态构建的表达式中有一个“额外”的转换调用,而硬编码版本似乎没有(或者不需要?)。该转换是在CallAny()实现中引入的。Linq-to-SQL似乎可以处理它,所以我保留了它(尽管它是不必要的)。我并不完全确定这种转换是否在比我的玩具示例更健壮的用法中可能需要。