【问题标题】:EF Core: filter condition for nested collection (Func<>) as a variableEF Core:嵌套集合的过滤条件(Func<>)作为变量
【发布时间】:2022-01-19 19:10:21
【问题描述】:

tl;博士; 我 .Net6 我想通过变量 Func 传递给 dbcontext.Entities.Include(host => host.nestedeCollection.Where(element => )) func element =>。它适用于 Z.Entityframework.Extensions,但无法将其转换为 EF Core。

说来话长。 考虑以下模型:

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 中实现相同的目标?有什么建议吗?

谢谢!

【问题讨论】:

标签: .net entity-framework linq .net-core entity-framework-core


【解决方案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 是目前的答案。 谢谢。

【讨论】:

  • 删除此消息并将答案标记为已回答。
  • 如果答案对您有帮助,请标记它。不要发布另一个答案
猜你喜欢
  • 2019-04-27
  • 2018-02-03
  • 2017-08-03
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多