使用 Func 作为 LinqToEntities 的参数

Posted

技术标签:

【中文标题】使用 Func 作为 LinqToEntities 的参数【英文标题】:Using Func as parameter for LinqToEntities 【发布时间】:2017-06-15 10:51:04 【问题描述】:

我有一个 Linq 查询来获取我的 EF 数据。我的查询连接了 4 个表并将结果选择为非规范化类型。我会经常需要这个查询,但有不同的谓词。 List-ExtensionMethods(例如 .Where() 使用 Func<T,bool> 作为参数,我也想这样做 - 但我找不到在我的方法中访问我的谓词的方法。

public DenormalizedType GetData(Func<Thing, bool> predicate)

    using (var dbContext = new MyDbContext())
     
        var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        // => HowToWhere ???
        select new DenormalizedType()
        
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        ).ToList();
    

关于这个问题,我有 3 个问题。

首先(显然):如何调用我的谓词来使用动态 where 子句?

第二:如果我最初的想法行不通(因为 teovankots 的回答表明,我的方法对 LinqToEntities 无效)是否有可能只创建一个连接方法?

第三:将我的结果返回到另一个软件组件的最佳执行方法是什么?

【问题讨论】:

【参考方案1】:

EF 查询提供程序需要将 LINQ 查询表达式树转换为 SQL,这在您传递 Func&lt;...&gt; 时是不可能的(更一般地说,调用表达式,如委托、未知方法等) .

简而言之,Func&lt;...&gt; 类型参数无法满足您的要求。

使用Queryable 方法时要考虑的第一件事是在Enumerable 方法中使用Func&lt;..&gt; 时使用Expression&lt;Func&lt;...&gt;&gt;。所以像这样改变你的方法参数:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)

不幸的是,现成不支持在 LINQ 查询语法中使用提供的表达式。为此,您需要一些表达式树处理库。

LINQKit 包页面中解释了问题和可能的解决方案。包提供的解决方案是通过AsExpandableInvoke自定义扩展方法。

安装nuget package,添加

using LinqKit;

到源代码文件,现在你可以使用这样的东西来实现目标:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)

    using (var dbContext = new MyDbContext())
     
        var myData = (from some in dbContext.Thing.AsExpandable() // <= 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        where predicate.Invoke(some) // <=
        select new DenormalizedType()
        
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        ).ToList();
    

【讨论】:

哦不...我工作了很多(检查我的答案)...非常感谢您花时间在这上面!我现在需要回家,但我明天会检查这个。如果您愿意,可以查看我的答案,非常感谢您的反馈! 好吧,一切都很好 :) 好消息是,一旦你意识到你需要表达式,这些方法可以组合 - 尽可能自定义 IQueryable&lt;T&gt; 方法,LinqKit 在其他地方(如它更通用)。按属性路径排序怎么样,我有自己的发现,类似于 How to use a string to create a EF order by expression? 中的 Marc(重新发明***):) 快乐编码和学习。 非常感谢!您的扩展程序也可以采用 MethodName 吗? 很遗憾没有。方法需要解析,所以DynamicLINQ包更适合这种场景。【参考方案2】:

至少我找到了解决我的问题的方法 - 但我非常感谢提示和/或使它变得更好的方法,因为我无法想象这是圣杯,甚至接近.. .

但是,第一步是我的加入,我以 IQueryable 的形式返回。重要提示:此处不要使用,否则 dbContext 将被释放,这在使用 IQueryable 时不太好:

private static MyDbContext _dbContext;
private static IQueryable<DenormalizedType> SelectType()

    _dbContext = new MyDbContext();
    var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        select new DenormalizedType()
        
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        ;
    return myData;

我今天学到了很多。例如:IEnumerable 和 IQueryable 都有一个扩展方法.Where()。但只有IEnumerable.Where()Func&lt;T,bool&gt; 作为参数。 IQueryable 将Expression&lt;Func&lt;T,bool&gt;&gt; 用作其Where()。如果我希望我的查询被执行,我需要在所有条件下使用 IQueryable 类型,只要我的所有 where 都没有执行。所以我需要仔细研究一下Expression-Type。我不明白这一切实际上做了什么,但它确实有效;)

我必须做的第一件事就是编写我的 Where-Methods。在我读完这篇文章后,这很容易:Entity Framework Filter "Expression<Func<T, bool>>"。方法如下所示:

public static IQueryable<DenormalizedType> SelectWhereCriteria(IQueryable<DenormalizedType> data, Expression<Func<DenormalizedType, bool>> predicate)

    return data.Where(predicate);

表达式本身有点复杂,因为我有一个 Selection-Enum 应该选择指定的过滤器。表达式看起来像:

Expression<Func<DenormalizedType, bool>> FilterBySelection(Selection selection)

    switch(selection)
    
        case Selection.Active:
            return x => x.IsActive == true;
        case Selection.InActive:
            return x => x.IsActive == false;
        case Selection.SomeOtherSelection:
            return x => x.SomeOther == "Criteria"
        default:
            return x => true;
    

这个表达式在我的 IQueryable 上运行良好:

var selectedQuery = DataHandler.SelectWhereCriteria(query, FilterBySelection(selection));

我现在唯一需要的就是订购。我从 MarcGravell(顺便说一句,真是个天才)Dynamic LINQ OrderBy on IEnumerable<T> 那里找到了一些非常酷的东西,他在其中将一些代码作为答案发布给 OrderBy PropertyName,您可以使用这些代码。他的第一段代码采用 IQueryable,按 PropertyName 对其进行排序(他还提供了 Descending OrderyBy 的扩展)并返回一个 IOrderedQueryable。 ToList()-Operation 是我执行的最后一个操作。

还有一件事:不要忘记 Dispose DbContext:

public static void Dispose()

    _dbContext.Dispose();

【讨论】:

不需要您的SelectWhereCriteria 方法。写var selectedQuery = query.Where(FilterBySelection(selection)); 谢谢!正如我现在所看到的...... ;)【参考方案3】:

您可以轻松调用它。安装this package,添加:

public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)

    using (var dbContext = new MyDbContext())
     
        var myData = (from some in dbContext.Thing 
        join other in dbContext.OtherThing
        on some.OtherId equals other.Id
        where predicate.Invoke(some) //check this
        select new DenormalizedType()
        
            SomeEntry = some.Entry
            SomeOtherId = some.OtherId
            OtherValue = other.Value
        ).ToList();
    

你应该知道Func&lt;T1,T2&gt; 是一个方法。使用此签名:

T2 predicate(T1 parameter)  /*...*/ 

您的第二个问题取决于您如何连接组件。但只要你得到 DenormalizedType 而不是 DbEntity,你的例子看起来还不错。

【讨论】:

这太容易了 ;) 不过:谢谢!!为什么我应该避免返回 DbEntity? IQueryable 的返回是什么? check this 在我看来就像运行时的​​NotSupportedException @Joshit 返回IQueryable 接口是一个好习惯。返回 DbEntity 不好,因为在复杂情况下可能会遇到序列化问题。 @IvanStoev 据我了解,OP 使用 linq-to-entities 确实 - 这正是我的意思。您的建议适用于 L2O,但会在 L2E 中产生上述异常。

以上是关于使用 Func 作为 LinqToEntities 的参数的主要内容,如果未能解决你的问题,请参考以下文章

使用 Func<T, string> lambda 动态构造 where 子句 - linq 到实体

带有 Sybase ASE 15.5 的 LINQToEntities

在 LinqToEntities 中,如何将动态列名传递给 DbFunctions.Like

Func<DomainObject,object> 如何将对象名称作为字符串返回 [重复]

函数对象和装饰器

python-函数对象应用