我会让代码说明一切:
public interface ISoftDeletable {
bool IsDeleted {get; set;}
}
public static class Extensions {
public IQueryable<T> Active<T>(this IQueryable<T> q) where T : ISoftDeletable {
return q.Where(t => !t.IsDeleted);
}
}
public partial class Thing : ISoftDeletable {
...
}
...
var query = from tc in db.ThingContainers
where tc.Things.Active().Any(t => t.SatisfiesOtherCondition)
select new { ... }; // throws System.NotSupportedException
错误是:
LINQ to Entities无法识别方法'System.Collections.Generic.IQueryable`1 [Thing] ActiveThing'方法,并且此方法无法转换为商店表达式。
你明白了:我想要一种流畅的表达方式,这样对于任何ISoftDeletable
我都可以在一个'where'子句中添加一个简单,可重复使用的代码。此处的示例不起作用,因为Linq2Entities不知道如何处理Active()
方法。
我在这里给出的示例很简单,但在我的实际代码中,Active()
扩展包含更复杂的条件,我不想复制和粘贴我的所有条件。代码。
有什么建议吗?
答案 0 :(得分:2)
代码中有两个不相关的问题。第一个是实体框架无法处理表达式中的强制转换,这是扩展方法所做的。
此问题的解决方案是向您的扩展方法添加class
限制。如果不添加该限制,则表达式将编译为包含强制转换:
.Where (t => !((ISoftDeletable)t.IsDeleted))
上面的强制转换会混淆Entity Framework,因此这就是您遇到运行时错误的原因。
添加限制后,表达式将变为简单的属性访问:
.Where (t => !(t.IsDeleted))
使用实体框架可以很好地解析这个表达式。
第二个问题是您无法在查询语法中应用用户定义的扩展方法,但您可以在Fluent语法中使用它们:
db.ThingContainers.SelectMany(tc => tc.Things).Active()
.Any(t => t.SatisfiesOtherCondition); // this works
要查看问题,我们必须查看实际生成的查询的内容:
db.ThingContainers
.Where(tc => tc.Things.Active().Any(t => t.StatisfiesOtherCondition))
.Select(tc => new { ... });
从不执行Active()调用,但是生成为要解析的EF的表达式。果然,EF不知道如何处理这样的功能,所以它很难解决。
明显的解决方法(尽管并非总是可行)是在Thing
而不是ThingContainers
开始查询:
db.Things.Active().SelectMany(t => t.Container);
另一种可能的解决方法是使用模型定义函数,但这是一个更复杂的过程。有关详细信息,请参阅this,this和this MSDN文章。
答案 1 :(得分:0)
虽然@felipe已经获得了答案,但我认为我也会发布自己的答案作为替代方案,尽管它是:
var query = from tc in db.ThingContainers.Active() // ThingContainer is also ISoftDeletable!
join t in db.Things.Active() on tc.ID equals t.ThingContainerID into things
where things.Any(t => t.SatisfiesOtherCondition)
select new { ... };
这有利于保持查询结构或多或少相同,但是你确实失去了ThingContainer
和Thing
之间隐含关系的流畅性。就我而言,交易的结果是明确指定关系,而不是明确指定Active()
标准。