将 LinqKit PredicateBuilder 用于相关模型(EF Core)

Posted

技术标签:

【中文标题】将 LinqKit PredicateBuilder 用于相关模型(EF Core)【英文标题】:Use LinqKit PredicateBuilder for related model (EF Core) 【发布时间】:2018-03-24 04:38:28 【问题描述】:

我想使用 LinqKit 的 PredicateBuilder 并将谓词传递给相关模型的 .Any 方法。

所以我想建立一个谓词:

var castCondition = PredicateBuilder.New<CastInfo>(true);

if (movies != null && movies.Length > 0)

    castCondition = castCondition.And(c => movies.Contains(c.MovieId));

if (roleType > 0)

    castCondition = castCondition.And(c => c.RoleId == roleType);

然后用它来过滤与谓词中的模型有关系的模型:

IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();

但这会导致System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

我看到similar question 并在那里回答建议使用.Compile。或者 one more question 构建一个额外的谓词。

所以我尝试使用额外的谓词

var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);

或者直接使用编译

IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));

但是我有一个关于编译的错误:System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'

那么是否可以将 PredicateBuilder 的结果转换为 Any

注意:我能够构建所需的行为组合表达式,但我不喜欢我需要额外的变量。

System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true);
if (movies != null && movies.Length > 0)

    castExpression = (c => movies.Contains(c.MovieId));

if (roleType > 0)

    var existingExpression = castExpression;
    castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType;

IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();

所以我想我只是想念一些关于 builder 的东西。

版本更新:我使用的是 dotnet core 2.0 和 LinqKit.Microsoft.EntityFrameworkCore 1.1.10

【问题讨论】:

【参考方案1】:

查看代码,可以假设castCondition 变量的类型是Expression&lt;Func&lt;CastInfo, bool&gt;&gt;(就像在早期版本的PredicateBuilder 中一样)。

但如果是这种情况,那么n.CastInfo.Any(castCondition) 甚至不应该编译(假设CastInfo 是一个集合导航属性,所以编译器将命中Enumerable.Any,它需要Func&lt;CastInfo, bool&gt;,而不是Expression&lt;Func&lt;CastInfo, bool&gt;&gt;)。那么这里发生了什么?

在我看来,这是 C# 隐式运算符滥用的一个很好的例子。 PredicateBuilder.New&lt;T&gt; 方法实际上返回了一个名为ExpressionStarter&lt;T&gt; 的类,它有许多模拟Expression 的方法,但更重要的是,它隐式 转换为Expression&lt;Func&lt;T, bool&gt;&gt;Func&lt;CastInfo, bool&gt;。后者允许将该类用于*** Enumerable / Queryable 方法作为相应 lambda func/expression 的替换。但是,在您的情况下,它还可以防止在表达式树中使用时出现编译时错误 - 编译器会发出类似 n.CastInfo.Any((Func&lt;CastInfo, bool&gt;)castCondition) 的内容,这当然会在运行时导致异常。

LinqKit AsExpandable 方法的整体思想是允许通过自定义Invoke 扩展方法“调用”表达式,然后在表达式树中“扩展”。所以回到开头,如果变量类型是Expression&lt;Func&lt;CastInfo, bool&gt;&gt;,那么预期的用法是:

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));

但是由于前面解释的原因,现在它无法编译。所以你必须先将其转换为查询的Expression&lt;Func&lt;T, bool&gt; outside

Expression<Func<CastInfo, bool>> castPredicate = castCondition;

然后使用

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));

为了让编译器推断表达式类型,我会创建一个这样的自定义扩展方法:

using System;
using System.Linq.Expressions;

namespace LinqKit

    public static class Extensions
    
        public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
    

然后简单地使用

var castPredicate = castCondition.ToExpression();

它仍然必须在查询之外完成,即以下操作不起作用

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));

【讨论】:

感谢您的详细解释。我试过你的代码,不幸的是我有一个警告The LINQ expression 'where __castCondition_0.Invoke([c])' could not be translated and will be evaluated locally. 所以当它编译和运行时,条件没有添加到 SQL 并且查询选择所有行。你有什么建议吗? 其实你提到ExpressionStarter可以施法到Expression。所以这有效var castExpr = (Expression&lt;Func&lt;CastInfo,bool&gt;&gt;)castCondition; context.Name.AsExpandable().Where(n =&gt; n.CastInfo.Any(castExpr.Compile())).Count();由于某种原因,内联变量不起作用 内联不起作用,因为“扩展器”无法识别 ExpressionStarter.Compile()。听起来像是不完整的 LinqKit 工作:( 啊,这就是让我留在这里的问答类型! (有时我想放弃)。 上面的建议也对我有用。我收到另一个错误,提示无法与 async/await 组合。应用上述提示并删除 await 和 To...Async() 后一切正常。谢谢!

以上是关于将 LinqKit PredicateBuilder 用于相关模型(EF Core)的主要内容,如果未能解决你的问题,请参考以下文章

如何在asp.net核心的for循环中使用linqkit

Entity Framework 4.1 Code First - 使用 LinqKit PredicateBuilder 时忽略包含

EntityFramework扩展之第三方类库

通过扩展方法过滤子查询

EF Core SQL 过滤器翻译

.AsExpandable 在 Linq to Entity