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()扩展包含了一组更复杂的条件,我不想在代码中到处复制和粘贴这些条件。

有什么建议吗?

IQueryable扩展方法的流利语法

代码中有两个不相关的问题。第一个问题是实体框架不能处理表达式中的强制转换,而扩展方法可以处理这种转换。

这个问题的解决方案是在扩展方法中添加一个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不知道该如何处理这样的函数,所以它退出了。

一个明显的解决方法(尽管并不总是可能的)是在Things而不是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 { ... };

这样做的优点是保持查询的结构大致相同,尽管您确实失去了ThingContainerThing之间隐含关系的流畅性。在我的情况下,权衡得出的结果是,最好显式指定关系,而不是必须显式指定Active()标准。