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

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

问题描述

我想使用 LinqKit 的 PredicateBuilder 并将谓词传递给相关模型的 .Any 方法.

所以我想构建一个谓词:

var castCondition = PredicateBuilder.New(true);if (movies != null &&movies.Length > 0){castCondition = castCondition.And(c =>movies.Contains(c.MovieId));}如果(角色类型 > 0){castCondition = castCondition.And(c => c.RoleId == roleType);}

然后用它来过滤与谓词中的模型有关系的模型:

IQueryable结果 = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();

但这会导致 System.NotSupportedException:无法解析表达式 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))':给定的参数与预期的参数不匹配:类型的对象System.Linq.Expressions.UnaryExpression"无法转换为类型System.Linq.Expressions.LambdaExpression".

我看到了类似的问题和那里的答案建议使用 .Compile.或者另一个问题来构建额外的谓词.>

所以我尝试使用额外的谓词

var tp = PredicateBuilder.New(true);tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));IQueryable<名称>结果 = _context.Name.AsExpandable().Where(tp);

或者直接使用compile

IQueryable结果 = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));

但是我有一个关于编译的错误:System.NotSupportedException:无法解析表达式'n.CastInfo.Any(__Compile_0)'

那么是否可以将 PredicateBuilder 的结果转换成Any?

注意:我能够构建所需的行为组合表达式,但我不喜欢我需要额外的变量.

System.Linq.Expressions.Expression>castExpression = (c => true);if (movies != null &&movies.Length > 0){castExpression = (c =>movies.Contains(c.MovieId));}如果(角色类型 > 0){var existingExpression = castExpression;castExpression = c =>existingExpression.Invoke(c) &&c.RoleId == roleType;}IQueryable<名称>结果 = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();

所以我想我只是想念有关 builder 的一些东西.

版本更新:我使用 dotnet core 2.0 和 LinqKit.Microsoft.EntityFrameworkCore 1.1.10

解决方案

看代码,会假设castCondition变量的类型是Expression>(就像在 PredicateBuilder 的早期版本中一样).

但如果是这样,那么 n.CastInfo.Any(castCondition) 甚至不应该编译(假设 CastInfo 是一个集合导航属性,所以编译器会点击 Enumerable.Any ,它需要 Func,而不是 Expression).那么这里发生了什么?

在我看来,这是 C# 隐式运算符滥用的一个很好的例子.PredicateBuilder.New 方法实际上返回一个名为 ExpressionStarter,它有很多模拟Expression的方法,但更重要的是,它有隐式转换为 ExpressionFunc.后者允许将该类用于顶级 Enumerable/Queryable 方法,以替代相应的 lambda 函数/表达式.但是,它也可以防止在表达式树中使用时出现编译时错误,就像您的情况一样 - 编译器发出类似 n.CastInfo.Any((Func)castCondition) 的内容当然在运行时会导致异常.

LinqKit AsExpandable 方法的整体思想是允许通过自定义的 Invoke 扩展方法调用"表达式,然后在表达式树中扩展".所以回到一开始,如果变量类型是Expression>,那么预期的用法是:

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

但是由于前面解释的原因,现在无法编译.所以你必须先把它转换成Expression outside 查询:

Expression>castPredicate = castCondition;

然后使用

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

_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));

为了让编译器推断表达式类型,我将创建一个自定义扩展方法,如下所示:

使用系统;使用 System.Linq.Expressions;命名空间 LinqKit{公共静态类扩展{公共静态表达式<Func<T, bool>>ToExpression<T>(这个ExpressionStarter<T> 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 Core)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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