12

我想使用 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);

或者直接使用compile

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

4

1 回答 1

12

查看代码,人们会假设castCondition变量的类型是Expression<Func<CastInfo, bool>>(就像在早期版本中一样PredicateBuilder)。

但如果是这样的话,那么n.CastInfo.Any(castCondition)甚至不应该编译(假设CastInfo是一个集合导航属性,所以编译器会命中Enumerable.Anywhich expects Func<CastInfo, bool>,而不是Expression<Func<CastInfo, bool>>)。那么这里发生了什么?

在我看来,这是 C# 隐式运算符滥用的一个很好的例子。该PredicateBuilder.New<T>方法实际上返回了一个名为 的类ExpressionStarter<T>,其中有许多方法 emulating Expression,但更重要的是,隐式转换为Expression<Func<T, bool>>and 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)));
于 2017-10-12T18:10:05.433 回答