IQueryable扩展方法的流利语法
本文关键字:语法 扩展 方法 IQueryable | 更新日期: 2023-09-27 18:29:28
我让代码自己说话:
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"子句。这里的示例不起作用,因为Linq2Enties不知道如何处理我的Active()
方法。
我在这里给出的例子很简单,但在我的真实代码中,Active()
扩展包含了一组更复杂的条件,我不想在代码中到处复制和粘贴这些条件。
有什么建议吗?
代码中有两个不相关的问题。第一个问题是实体框架不能处理表达式中的强制转换,而扩展方法可以处理这种转换。
这个问题的解决方案是在扩展方法中添加一个class
限制。如果不添加该限制,表达式将被编译为包含强制转换:
.Where (t => !((ISoftDeletable)t.IsDeleted))
上面的强制转换混淆了实体框架,所以这就是为什么会出现运行时错误的原因。
添加限制后,表达式将变为简单的属性访问:
.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
s而不是ThingContainers
:启动查询
db.Things.Active().SelectMany(t => t.Container);
另一种可能的解决方法是使用模型定义函数,但这是一个更复杂的过程。有关详细信息,请参阅此、此和此MSDN文章。
虽然@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()
标准。