将LinqKit PredicateBuilder用于相关模型(EF核心) [英] Use LinqKit PredicateBuilder for related model (EF Core)
问题描述
我想使用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'.
我看到了类似的问题,答案是建议使用.Compile
.或还有一个问题,它会建立一个额外的谓词.>
所以我尝试使用额外的谓词
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 core 2.0和LinqKit.Microsoft.EntityFrameworkCore 1.1.10
看一下代码,我们将假定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)));
I want to use LinqKit's PredicateBuilder and pass the predicate into .Any
method for related model.
So I want to build a predicate:
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);
}
And then use it to filter model that has relation to model in predicate:
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
But this causes a 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'.
I saw similar question and answer there suggests to use .Compile
. Or one more question that build an extra predicate.
So I tried to use extra predicate
var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);
Or use compile directly
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));
But I have an error about Compile: System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'
So is it possible to convert the result from PredicateBuilder to pass into Any
?
Note: I was able to build the desired behavior combining expressions, but I don't like that I need extra variables.
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();
So I assume I just miss something about builder.
Update about versions: I use dotnet core 2.0 and LinqKit.Microsoft.EntityFrameworkCore 1.1.10
Looking at the code, one will assume that the type of castCondition
variable is Expression<Func<CastInfo, bool>>
(as it was in earlier versions of PredicateBuilder
).
But if that was the case, then n.CastInfo.Any(castCondition)
should not even compile (assuming CastInfo
is a collection navigation property, so the compiler will hit Enumerable.Any
which expects Func<CastInfo, bool>
, not Expression<Func<CastInfo, bool>>
). So what's going on here?
In my opinion, this is a good example of C# implicit operator abuse. The PredicateBuilder.New<T>
method actually returns a class called ExpressionStarter<T>
, which has many methods emulating Expression
, but more importantly, has implicit conversion to Expression<Func<T, bool>>
and Func<CastInfo, bool>
. The later allows that class to be used for top level Enumerable
/ Queryable
methods as replacement of the respective lambda func/expression. However, it also prevents the compile time error when used inside the expression tree as in your case - the complier emits something like n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
which of course causes exception at runtime.
The whole idea of LinqKit AsExpandable
method is to allow "invoking" expressions via custom Invoke
extension method, which then is "expanded" in the expression tree. So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>
, the intended usage is:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
But now this doesn't compile because of the reason explained earlier. So you have to convert it first to Expression<Func<T, bool>
outside of the query:
Expression<Func<CastInfo, bool>> castPredicate = castCondition;
and then use
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
or
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
To let compiler infer the expression type, I would create a custom extension method like this:
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;
}
}
and then simply use
var castPredicate = castCondition.ToExpression();
It still has to be done outside of the query, i.e. the following does not work:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
这篇关于将LinqKit PredicateBuilder用于相关模型(EF核心)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!