2017-10-12 82 views
4

我想使用LinqKit的PredicateBuilder并将谓词传递给.Any方法以获得相关模型。使用LinqKit PredicateBuilder进行相关模型(EF Core)

所以我想建立一个断言:

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(); 

所以我想我只是想念一些关于建设者。

更新有关版本:我用的dotnet核心2.0和LinqKit.Microsoft.EntityFrameworkCore 1.1.10

回答

4

看代码,一个将假定castCondition变量的类型是Expression<Func<CastInfo, bool>>(因为它是在前面版本为PredicateBuilder)。

但是,如果是这样的话,那么n.CastInfo.Any(castCondition)甚至不应该编译(假设CastInfo是一家集导航属性,所以编译器会打Enumerable.Any其预计Func<CastInfo, bool>,不Expression<Func<CastInfo, bool>>)。那么这里发生了什么?

在我看来,这是C#隐式操作符滥用的一个很好的例子。该PredicateBuilder.New<T>方法实际上返回一个名为ExpressionStarter<T>类,它有很多方法模拟Expression,但更重要的是,有转换为Expression<Func<T, bool>>Func<CastInfo, bool>。后者允许该类用于顶层Enumerable/Queryable方法作为替换各自的lambda func/expression。但是,它也可以防止在表达式树中使用编译时错误 - 编译器会发出类似n.CastInfo.Any((Func<CastInfo, bool>)castCondition)的东西,这在运行时会导致异常。

LinqKit AsExpandable方法的整个想法是允许通过自定义Invoke扩展方法“调用”表达式,然后在表达式树中“扩展”方法。因此,回到开头,如果变量类型为Expression<Func<CastInfo, bool>>,使用目的是:

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

但现在,这并不编译,因为的理由如前所述。所以,你必须先将其转换为Expression<Func<T, bool>外查询的

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))); 
+2

感谢详细的解释。我试过你的代码,不幸的是我有一个'LINQ表达式'的警告,其中__castCondition_0.Invoke([c])'不能被翻译,并且会在本地进行评估。'所以,当它编译并运行时,条件不会被添加到SQL和查询选择所有行。你有什么建议吗? – Igor

+0

其实你提到'ExpressionStarter'可以用于'Expression'的施法者。所以这工作'var castExpr =(Expression >)castCondition; context.Name.AsExpandable()。其中​​(n => n.CastInfo.Any(castExpr.Compile()))。Count()';内联变量不起作用出于某种原因 – Igor

+0

内联不起作用,因为“扩展器”无法识别“ExpressionStarter.Compile()”。听起来像不完整的LinqKit工作:( –

相关问题