[英]Use LinqKit PredicateBuilder for related model (EF Core)
I want to use LinqKit's PredicateBuilder and pass the predicate into .Any
method for related model.我想使用 LinqKit 的 PredicateBuilder 并将谓词传递给相关模型的
.Any
方法。
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'.
但这会导致
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
.我看到类似的问题和答案建议使用
.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或者直接使用compile
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)'
但我有一个关于编译的错误:
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
?那么是否可以将 PredicateBuilder 的结果转换为
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版本更新:我使用 dotnet core 2.0 和 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
).查看代码,我们会假设
castCondition
变量的类型是Expression<Func<CastInfo, bool>>
(就像在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>>
).但如果是这种情况,那么
n.CastInfo.Any(castCondition)
甚至不应该编译(假设CastInfo
是一个集合导航属性,所以编译器会命中Enumerable.Any
期望Func<CastInfo, bool>
,而不是Expression<Func<CastInfo, bool>>
)。 So what's going on here?那么这里发生了什么?
In my opinion, this is a good example of C# implicit operator abuse.在我看来,这是 C# 隐式运算符滥用的一个很好的例子。 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>
. PredicateBuilder.New<T>
方法实际上返回一个名为ExpressionStarter<T>
的类,它有许多模拟Expression
的方法,但更重要的是,隐式转换为Expression<Func<T, bool>>
和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.后者允许将该类用于顶级
Enumerable
/ Queryable
方法,以替换相应的 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.但是,在您的情况下,它还可以防止在表达式树中使用时出现编译时错误 - 编译器会发出类似
n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
的内容,这当然会在运行时导致异常。
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. LinqKit
AsExpandable
方法的整个想法是允许通过自定义Invoke
扩展方法“调用”表达式,然后在表达式树中“扩展”。 So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>
, the intended usage is:所以回到开头,如果变量类型是
Expression<Func<CastInfo, bool>>
,那么预期的用法是:
_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<T, bool>
:
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, ie the following does not work:它仍然必须在查询之外完成,即以下内容不起作用:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
It may not be exactly related to the original question, but considering the following model :它可能与原始问题不完全相关,但考虑以下模型:
public Class Music
{
public int Id { get; set; }
public List<Genre> Genres { get; set; }
}
public Class Genre
{
public int Id { get; set; }
public string Title { get; set; }
}
List<string> genresToFind = new() {"Pop", "Rap", "Classical"};
If you are trying to find all Musics
that their genres exist in genresToFind
list, here's what you can do:如果您尝试查找其流派存在于
Musics
列表中genresToFind
所有音乐,您可以执行以下操作:
Create PredicateBuilder
expressions chain on Genre
model :在
Genre
模型上创建PredicateBuilder
表达式链:
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
Then execute your query like this :然后像这样执行您的查询:
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression()
is a generic extension method that we've created to convert ExpressionStarter<Genre>
type to Expression<Func<Genre, bool>>
: ToExpression()
是我们创建的通用扩展方法,用于将ExpressionStarter<Genre>
类型转换为Expression<Func<Genre, bool>>
:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
Also, you'll need LinqKit.Microsoft.EntityFrameworkCore
package for efcore.此外,您还需要用于 efcore 的
LinqKit.Microsoft.EntityFrameworkCore
包。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.