C# IQueryable扩展方法的流畅语法?

C# IQueryable扩展方法的流畅语法?,c#,linq,linq-to-entities,entity-framework-5,C#,Linq,Linq To Entities,Entity Framework 5,我将让代码自己说话: 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 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
公共接口ISoftDeletable{
布尔被删除{get;set;}
}
公共静态类扩展{
公共IQueryable活动(此IQueryable q),其中T:ISoftDeletable{
返回q.Where(t=>!t.IsDeleted);
}
}
公共部分类事物:ISoftDeletable{
...
}
...
var query=来自db.ThingContainers中的tc
其中tc.Things.Active().Any(t=>t.SatisfiesOtherCondition)
选择新的{…};//抛出System.NotSupportedException
错误是:

LINQ to Entities无法识别方法'System.Collections.Generic.IQueryable'1[Thing]ActiveThing'方法,并且无法将此方法转换为存储表达式

你明白了:我想要一种流畅的表达方式,这样对于任何
ISoftDeletable
,我都可以用一段简单的、可重用的代码添加一个“where”子句。这里的示例不起作用,因为Linq2Entities不知道如何处理我的
Active()
方法

我在这里给出的示例很简单,但在我的实际代码中,
Active()
扩展包含一组复杂得多的条件,我不想在我的代码中复制和粘贴这些条件


有什么建议吗?

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

此问题的解决方案是向扩展方法添加
限制。如果不添加该限制,则将编译表达式以包含强制转换:

.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
处而不是
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()
条件。

您没有说明您将得到什么异常,这使得帮助您变得更加困难。我希望您到目前为止所做的一切都很好,但更复杂的示例则不行,例如,在联接中使用
Active
。@JonSkeet-添加了错误消息。但是,是的,你是对的,我的实际用法确实涉及一个连接。你用你的简单代码重现了这个错误吗?我怀疑不是这样的-如果有重现错误的示例代码会很有用。@JonSkeet-编辑以显示更接近我实际代码的内容。显然,我无法编译以查看这是否真的失败,但这比将我的实际代码强加给您要好@JonSkeet,顺便说一句,你有没有注意到我对代码片段使用了与你在C中使用的相同的约定我很喜欢这本书-谢谢!不过,它确实在一个相关的方面帮助了我,因此+1表示感谢@肖尔,我已经扩展了答案。基本上,将查询语法与扩展方法混合在一起是一件痛苦的事情。很好,但是如果
ThingContainer
也是
ISoftDeletable
,并且我想将
.Active()
db.Things.Active().SelectMany(t=>t.Container.Active()
;)。如果查询足够复杂的话,它肯定会导致你不得不重复这个表达式。谢谢你的帮助!