EF Core:嵌套集合的过滤条件(Func<>)作为变量
Posted
技术标签:
【中文标题】EF Core:嵌套集合的过滤条件(Func<>)作为变量【英文标题】:EF Core: filter condition for nested collection (Func<>) as a variable 【发布时间】:2022-01-19 19:10:21 【问题描述】:tl;博士;
我 .Net6 我想通过变量 Func
说来话长。 考虑以下模型:
public class Attendance
public Guid Id get; set;
public Guid StudentId get; set;
public string Subject get; set;
public decimal Rank get; set;
public class Student
public Guid Id get; set;
public string Name get; set;
public virtual ICollection<Attendance> Attendances get; set;
public class UniversityDbContext : DbContext
public DbSet<Attendance> Attendances get; set;
public DbSet<Student> Students get; set;
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Student>()
.HasMany(student => student.Attendances)
.WithOne()
.HasForeignKey(x => x.StudentId)
;
我想:
var students = db
.Students
.Include(student => student.Attendances.Where(att => att.Rank < 3.0m))
.ToList()
;
目前我正在使用 Z.EntityFramework.Extensions .IncludeOptimized。由于 Net5 有一个 EF-native 功能来执行相同的任务,并且上面的代码对于上面 Where(att => att.Rank
Func<Attendance, bool> func = attendance => attendance.Rank < 3.0m;
var students = db
.Students
.IncludeOptimized(student => student.Attendances.Where(func))
.ToList()
;
成功了。
现在,我尝试使用 NET6 和最新可用的 EF Core。所有尝试均失败:
Func<Attendance, bool> func_x = attendance => true;
Func<Attendance, bool> func_y = attendance => attendance.Rank < 3.0m;
Expression<Func<Attendance, bool>> expr = att => att.Rank < 3.0m;
//*
using (var db = new UniversityDbContext(connString))
var students_x = db
.Students
.Include(student => student.Attendances.Where(func_x))
.ToList()
;
//**
//using (var db = new UniversityDbContext(connString))
// var students_z = db
// .Students
// .Include(student => student.Attendances.Where(x => func_x(x)))
// .ToList()
// ;
//
//***
//using (var db = new UniversityDbContext(connString))
// var students_y = db
// .Students
// .Include(student => student.Attendances.Where(func_y))
// .ToList()
// ;
//
//****
//using (var db = new UniversityDbContext(connString))
// var students_z = db
// .Students
// .Include(student => student.Attendances.Where(Func_z))
// .ToList()
// ;
//
//*****
//using (var db = new UniversityDbContext(connString))
// var students_expr = db
// .Students
// .Include(student => student.Attendances.Where(expr))
// .ToList()
// ;
//
//* 失败
System.ArgumentException: Expression of type 'System.Func`2[FuncInEFIncludeExample.Attendance,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[FuncInEFIncludeExample.Attendance,System.Boolean]]' of method 'System.Linq.IQueryable`1[FuncInEFIncludeExample.Attendance] Where[Attendance](System.Linq.IQueryable`1[FuncInEFIncludeExample.Attendance], System.Linq.Expressions.Expression`1[System.Func`2[FuncInEFIncludeExample.Attendance,System.Boolean]])' (Parameter 'arg1')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1)
at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
at System.Linq.Expressions.Expression.Call(MethodInfo method, IEnumerable`1 arguments)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.TryConvertEnumerableToQueryable(MethodCallExpression methodCallExpression)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at FuncInEFIncludeExample.Program.Main() in W:\Projects\FuncInEFInclude\FuncInEFIncludeExample\Program.cs:line 81
//** 失败
System.InvalidOperationException: The LINQ expression 'DbSet<Attendance>()
.Where(a => EF.Property<Guid?>(EntityShaperExpression:
FuncInEFIncludeExample.Student
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id") != null && object.Equals(
objA: (object)EF.Property<Guid?>(EntityShaperExpression:
FuncInEFIncludeExample.Student
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id"),
objB: (object)EF.Property<Guid?>(a, "StudentId")))
.Where(a => Invoke(__func_x_0, a)
)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& )
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.IncludeExpression.VisitChildren(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.VisitExtension(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitExtension(Expression extensionExpression)
at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at FuncInEFIncludeExample.Program.Main() in W:\Projects\FuncInEFInclude\FuncInEFIncludeExample\Program.cs:line 91
其余的都失败为//*,最后一个,//*****,正如预期的那样,甚至没有编译。
我进一步尝试使用嵌套集合,使它们成为 IQueryable。无法达到我想要的。我特别困惑
System.ArgumentException: Expression of type 'System.Func`2[FuncInEFIncludeExample.Attendance,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[FuncInEFIncludeExample.Attendance,System.Boolean]]' of method 'System.Linq.IQueryable`1[FuncInEFIncludeExample.Attendance]
在访问者模式实现过程中是否存在无效转换(查看堆栈跟踪)?
这就是我的问题。 有没有人尝试在 EF Core 中实现相同的目标?有什么建议吗?
谢谢!
【问题讨论】:
所以您使用 Z.EntityFramework.Extensions 的IncludeOptimized
将 Func<,bool>
转换为不再有效的 Expression<Func<,bool>>
。这不是 EF Core 问题。 实际的表达式是什么?如果您使用的表达式无法转换为 SQL,则任何扩展都不起作用
你到底想做什么?除了接受Expression
的查询之外,您编写的任何查询都不会起作用,正如您所说,.Include(student => student.Attendances.Where(att => att.Rank < 3.0m))
有效。如果要动态传递表达式,请检查LINQKit。
@PanagiotisKanavos,用 Z。我可以做 Func func = a => a.Rank;然后只需将 func 提供给 .Where 由 Z 进行的扩展。现在 EF-native 的相同逻辑不起作用。而对于 Expression,问题在于 .Where() 采用 Func,而不是 Expression。无论如何,感谢 LINQKit 的链接——我明天会检查它,但只是认为没有 EF-native 过滤也可以实现。
它从来没有奏效。以前没有,现在也没有。 EF 和 EF Core 处理的是 Expression 而不是 Func。 Z.EntityFramework.Extensions 是第三方付费、封闭源代码 扩展。 Z 所做的就是将该 Func 转换为一个表达式。现在 Z 没能做到这一点。由于您为此付费,您可以要求供应商解决问题。
@PanagiotisKanavos, a) .Include 与 .Where 子句一起用于嵌套集合,因为 EFCore 在 NET5 中。 .Where 接受 Func (因为它在上面的模型中是 IEnumerable ) b)Z.Entity 现在没有失败,它仍在工作。我只是想摆脱它以支持(如果可用)EFCore-native 功能。
【参考方案1】:
如果你想要查询翻译,你只需要处理Expression
,只是Func
不能翻译成SQL。好消息,您不需要 Z.EntityFramework
,坏消息是 EF Core 仍然不会翻译您的查询。
您需要 ligthweigh 库 LINQKit。它只需要配置 DbContextOptions:
builder
.UseSqlServer(connectionString)
.WithExpressionExpanding(); // enabling LINQKit extension
然后你可以通过Invoke
扩展使用你的表达式:
Expression<Func<Attendance, bool>> func_x = attendance => true;
Expression<Func<Attendance, bool>> func_y = attendance => attendance.Rank < 3.0m;
Expression<Func<Attendance, bool>> expr = att => att.Rank < 3.0m;
using (var db = new UniversityDbContext(connString))
var students_x = db
.Students
.Include(student => student.Attendances.Where(x => func_x.Invoke(x)))
.ToList();
【讨论】:
谢谢,明天试试。 Svyatoslav, Panagiotos -- 谢谢。我确认 LINQKit 解决了这个问题(尽管我想在没有 3rd-party 库的情况下实现它)。 可以肯定,但您有时间将您的注入到 EF Core 服务并纠正表达式树吗? @rezdm 但是....您已经使用了第三方扩展。您想要做的事情从未与 EF 合作过。 @rezdm 事实上,鉴于 Z.EntityFramework.Extensions 是一个付费的闭源产品,你为什么还要在这里发布问题?你不应该联系供应商吗?【参考方案2】:正如建议的那样,LINQKit 是目前的答案。 谢谢。
【讨论】:
删除此消息并将答案标记为已回答。 如果答案对您有帮助,请标记它。不要发布另一个答案以上是关于EF Core:嵌套集合的过滤条件(Func<>)作为变量的主要内容,如果未能解决你的问题,请参考以下文章
Ef core LazyLoading - 访问集合类型的嵌套导航属性引发 DetachedLazyLoadingWarning 错误