简体   繁体   English

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM