将 LinqKit PredicateBuilder 用于相关模型 (EF Core)
Use LinqKit PredicateBuilder for related model (EF Core)
我想使用 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'.
我看到 similar question and answer there suggests to use .Compile
. Or one more question 构建了一个额外的谓词。
所以我尝试使用额外的谓词
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>
方法实际上 returns 一个叫做 ExpressionStarter<T>
的 class,它有很多模仿 Expression
的方法,但更重要的是,有 implicit 转换为 Expression<Func<T, bool>>
和 Func<CastInfo, bool>
。后者允许 class 用于顶级 Enumerable
/ Queryable
方法,作为相应 lambda func/expression 的替换。但是,它也可以防止在表达式树中使用时出现编译时错误,就像您的情况一样 - 编译器会发出类似 n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
的内容,这当然会在运行时导致异常。
LinqKit AsExpandable
方法的整个思想是通过自定义 Invoke
扩展方法允许 "invoking" 表达式,然后在表达式树中是 "expanded"。所以回到开头,如果变量类型是Expression<Func<CastInfo, bool>>
,预期的用法是:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
但由于前面解释的原因,现在无法编译。因此,您必须先将其转换为查询的 Expression<Func<T, bool>
outside:
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)));
它可能与原始问题不完全相关,但考虑以下模型:
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"};
如果您要查找所有 Musics
其流派存在于 genresToFind
列表中,您可以执行以下操作:
在 Genre
模型上创建 PredicateBuilder
表达式链:
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
然后像这样执行查询:
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression()
是我们创建的通用扩展方法,用于将 ExpressionStarter<Genre>
类型转换为 Expression<Func<Genre, bool>>
:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
另外,您需要 LinqKit.Microsoft.EntityFrameworkCore
efcore 包。
我想使用 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'.
我看到 similar question and answer there suggests to use .Compile
. Or one more question 构建了一个额外的谓词。
所以我尝试使用额外的谓词
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>
方法实际上 returns 一个叫做 ExpressionStarter<T>
的 class,它有很多模仿 Expression
的方法,但更重要的是,有 implicit 转换为 Expression<Func<T, bool>>
和 Func<CastInfo, bool>
。后者允许 class 用于顶级 Enumerable
/ Queryable
方法,作为相应 lambda func/expression 的替换。但是,它也可以防止在表达式树中使用时出现编译时错误,就像您的情况一样 - 编译器会发出类似 n.CastInfo.Any((Func<CastInfo, bool>)castCondition)
的内容,这当然会在运行时导致异常。
LinqKit AsExpandable
方法的整个思想是通过自定义 Invoke
扩展方法允许 "invoking" 表达式,然后在表达式树中是 "expanded"。所以回到开头,如果变量类型是Expression<Func<CastInfo, bool>>
,预期的用法是:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
但由于前面解释的原因,现在无法编译。因此,您必须先将其转换为查询的 Expression<Func<T, bool>
outside:
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)));
它可能与原始问题不完全相关,但考虑以下模型:
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"};
如果您要查找所有 Musics
其流派存在于 genresToFind
列表中,您可以执行以下操作:
在 Genre
模型上创建 PredicateBuilder
表达式链:
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
然后像这样执行查询:
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression()
是我们创建的通用扩展方法,用于将 ExpressionStarter<Genre>
类型转换为 Expression<Func<Genre, bool>>
:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
另外,您需要 LinqKit.Microsoft.EntityFrameworkCore
efcore 包。