将LinqKit PredicateBuilder用于相关模型(EF核心) [英] Use LinqKit PredicateBuilder for related model (EF Core)

查看:119
本文介绍了将LinqKit PredicateBuilder用于相关模型(EF核心)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆