我希望使用Expression<Func<T,bool>>
减少LinqToSQL查询中的重复逻辑。我们已成功完成之前使用静态属性,如下所示:
public static Expression<Func<Document, bool>> IsActive
{
get
{
return document => !document.Deleted;
}
}
...
_workspace.GetDataSource<Document>().Where(DocumentSpecifications.IsActive)
然而,当我需要将其他参数传递到Expression中时,我正在努力使其工作:
public static Expression<Func<Comment, bool>> IsUnread(int userId, Viewed viewed)
{
return
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > viewed.LastViewedDate);
}
...
// Throwing "Argument type 'System.Linq.Expression<X,bool>' is not assignable
// to parameter type 'System.Func<X,bool>'"
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView))
})
如何转换规范以便以这种方式接受它并且它是否仍然可以正确转换为SQL?
编辑:在Anders建议之后,我将.Compile()添加到查询中。它现在可以在内存集合中进行单元测试时正常工作但是当LinqToSQL尝试将其转换为SQL时,我得到以下异常:
System.NotSupportedException:用于查询运算符'Count'的不支持的重载
我试过了:
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
a.Comments.AsQueryable().Count(CommentSpecifications.IsUnread(actorId, a.LastView))
答案 0 :(得分:3)
看起来第二个查询是作为linq-to-objects而不是linq-to-sql执行的。它期望Func<X, bool>
是linq-to-objects使用的,而linq-to-sql(或任何其他IQueryable
提供者)期望一个未编译的表达式树可以转换为其他东西)
快速解决方法是在表达式上调用Compile()
以将其转换为可执行函数。
a.Comments.Count(CommentSpecifications.IsUnread(actorId, a.LastView).Compile())
为了更详细,你真的应该弄清楚为什么该查询作为linq-to-objects而不是linq-to-sql执行。特别是如果你期望它被翻译成高效的sql,它可能会成为一场性能噩梦。
编辑之后,更明显的是发生了什么:
您在单元测试期间将查询作为linq-to-objects运行,并在以后作为linq-to-sql运行。在这种情况下,将表达式转换为Func<>
到Compile()
将不起作用,因为linq-to-sql无法识别它。
将可重用部分组合到要翻译的查询表达式中很难 - 它会混淆翻译引擎。 Linq-to-sql比linq-to-entity更宽容,但是很难让它工作。更好的方法是制作在IQueryable<T>
上运行的链接功能。
public static IQueryable<Comment> WhereIsUnread(this IQueryable<Comment> src, int userId)
{
return src.Where(
c =>
!c.Deleted && c.CreatedByActorID != actorId
&& (viewed == null || c.ItemCreatedDate > c.Alert.LastView.LastViewedDate));
}
...
return (from a in alerts
select
new UnreadComments
{
TotalNumberOfUnreadComments =
a.Comments.WhereIsUnRead(actorId).Count()
})
这样的事情应该有用。请注意,我已经重写了上次查看日期的访问方式,因为当作为参数传入时,它将无法转换为SQL。