【问题标题】:Generic expression for EF Core 3.1 filtering throws exception on .Any(...)EF Core 3.1 过滤的通用表达式在 .Any(...) 上引发异常
【发布时间】:2020-08-31 09:55:36
【问题描述】:

我尝试创建一种通过 EF Core 3.1 获取数据的通用方式,并对不同的子项进行相同的过滤。为此,我尝试在 Any(...) 中提取搜索到的表达式。

public Expression<Func<PraeparatUpdateEntity, bool>> IsToApprovePackungUpdates_Working()
{
   return entity => entity.PackungUpdates.Any(e => !e.IsImported
      && e.UpdateState != EntityUpdateState.Accepted
      && e.UpdateType != EntityUpdateType.Unchanged);
}

public Expression<Func<PraeparatUpdateEntity, bool>> IsToApprovePackungUpdates_NotWorking()
{
   var func = new Func<PackungUpdateEntity, bool>(e => !e.IsImported
      && e.UpdateState != EntityUpdateState.Accepted
      && e.UpdateType != EntityUpdateType.Unchanged);

   return entity => entity.PackungUpdates.Any(func);
}

public new async Task<ICollection<PraeparatUpdateEntity>> GetToApproveAsync(bool trackChanges = false)
{ 
   var query = Set.Include(praeparatUpdateEntity => praeparatUpdateEntity.PackungUpdates)
      .Where(IsToApprovePackungUpdates_NotWorking());
            
   if (!trackChanges)
   {
      query = query.AsNoTracking();
   }

   return await query.ToListAsync();
}

第一个版本正在运行。 第二个失败并显示错误消息:

System.ArgumentException : Expression of type 'System.Func`2[MyProject.Data.Common.Entities.Update.PackungUpdateEntity,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[MyProject.Data.Common.Entities.Update.PackungUpdateEntity,System.Boolean]]' of method 'Boolean Any[PackungUpdateEntity](System.Linq.IQueryable`1[MyProject.Data.Common.Entities.Update.PackungUpdateEntity], System.Linq.Expressions.Expression`1[System.Func`2[MyProject.Data.Common.Entities.Update.PackungUpdateEntity,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 Microsoft.EntityFrameworkCore.Query.Internal.EnumerableToQueryableMethodConvertingExpressionVisitor.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 System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   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__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at MyProject.Data.Repositories.PraeparatUpdateRepository.GetToApproveAsync(Boolean trackChanges) in C:\git\MyProject\Source\MyProject.Data\Repositories\PraeparatUpdateRepository.cs:line 156
   at MyProject.Data.Tests.Integration.RepositoryNavigationPropertyLoadingTests.GetAllPraeparatUpdates_WhereToApprove_WithNavigationProperties_OK_Test() in C:\git\MyProject\Source\MyProject.Data.Tests.Integration\RepositoryNavigationPropertyLoadingTests.cs:line 328
--- End of stack trace from previous location where exception was thrown ---

********更新********

如果我将 AsQueryable() 添加到我的 IEnumerable 数据库子项中,我可以像这样添加我的表达式:

var query = Set.Include(praeparatUpdateEntity => praeparatUpdateEntity.PackungUpdates)
   .Include(praeparatUpdateEntity => praeparatUpdateEntity.SequenzUpdates)
   .ThenInclude(sequenzUpdateEntity => sequenzUpdateEntity.ApplikationsartUpdates)
   .Include(praeparatUpdateEntity => praeparatUpdateEntity.SequenzUpdates)
   .ThenInclude(sequenzUpdateEntity => sequenzUpdateEntity.DeklarationUpdates)
   .Where(IsToApprove<PraeparatUpdateEntity>()
      .OrElse(entity => entity.PackungUpdates.AsQueryable().Any(IsToApprove<PackungUpdateEntity>()))
      .OrElse(entity => entity.SequenzUpdates.AsQueryable().Any(IsToApprove<SequenzUpdateEntity>()))
      .OrElse(entity => entity.SequenzUpdates.SelectMany(sequenzUpdateEntity => sequenzUpdateEntity.ApplikationsartUpdates).AsQueryable()
         .Any(IsToApprove<ApplikationsartUpdateEntity>()))
      .OrElse(entity => entity.SequenzUpdates.SelectMany(sequenzUpdateEntity => sequenzUpdateEntity.DeklarationUpdates).AsQueryable()
         .Any(IsToApprove<DeklarationUpdateEntity>())));

和我的通用表达式:

public Expression<Func<T, bool>> IsToApprove<T>() where T : class, IUpdateEntity
{
   return entity => !entity.IsImported && entity.UpdateState != EntityUpdateState.Accepted
   && entity.UpdateType != EntityUpdateType.Unchanged;
}

目前看来可行...正在进行测试

【问题讨论】:

  • If I add AsQueryable() to my IEnumerable database children 他们已经是IQueryable,而不是IEnumerableDbSet&lt;T&gt; 实现了 IQueryable&lt;T&gt;,这就是您可以使用 LINQ 的原因。问题是您的“通用表达式”实际上是使用 Any 的特定函数调用,无法转换为 SQL。真正起作用的特定实体上的表达式。你想做什么?不管它是什么不需要这么复杂的表达方式
  • 您是否要过滤子实体?您不能在 EF Core 3.1 中执行此操作,它将在 EF Core 5 中提供。过滤子实体与在查询中应用所有过滤器相同,之后JOIN 产生大量空行或重复行。您需要过滤 JOIN 子句本身,这在 EF Core 3.1 中是不可能的。在 SQL 中,您需要 parent LEFT JOIN (select ... from child where child.IsApproved =10 c on c.ParentID=parent.ID
  • 鉴于所有这些条件都是硬编码的,您可以创建仅返回匹配对象并将您的实体映射到它们的 SQL Server 视图或函数
  • 我正在尝试获取 PraeparateUpdateEntities 列表(包含所有子项),其中需要批准实体本身或其任何子项的八倍。所以我不需要过滤任何子实体。

标签: c# ef-core-3.1


【解决方案1】:

Entity Framework 建立在使用expression treesIQueryable 之上。这些是在运行时分析您的代码并将其转换为 SQL 所必需的。首先,sn-p 编译器为您承担了构建表达式树的所有繁重工作。您可以尝试自己构建表达式树,但通常不是那么简单的任务。在这种情况下,您可以尝试以下操作:

Expression<Func<PackungUpdateEntity, bool>> exp = e => !e.IsImported
      && e.UpdateState != EntityUpdateState.Accepted
      && e.UpdateType != EntityUpdateType.Unchanged;

var any = typeof(Enumerable)
    .GetMethods()
    .Where(mi => mi.Name == nameof(Enumerable.Any) && mi.GetParameters().Length == 2)
    .Single()
    .MakeGenericMethod(typeof(PackungUpdateEntity));
var param = Expression.Parameter(typeof(PraeparatUpdateEntity));
var toAny = Expression.PropertyOrField(param, nameof(PraeparatUpdateEntity.PackungUpdates));
var call = Expression.Call(any, toAny, exp); 
var result = Expression.Lambda<Func<PraeparatUpdateEntity, bool>>(call, param);

return result;

【讨论】:

  • 导致编译器错误:CS1503 Argument 2: cannot convert from 'System.Linq.Expressions.Expression> ' 到 'System.Func'
  • 您还需要将返回变量存储到表达式中。
  • @Angelika entity.PackungUpdates 应该是 IQueryable&lt;PackungUpdateEntity&gt;
  • 如果我像 public Expression&lt;Func&lt;PraeparatUpdateEntity, bool&gt;&gt; IsToApprovePackungUpdates_MayBeWorking() { Expression&lt;Func&lt;PackungUpdateEntity, bool&gt;&gt; func = e =&gt; !e.IsImported &amp;&amp; e.UpdateState == EntityUpdateState.Accepted &amp;&amp; e.UpdateType != EntityUpdateType.Unchanged; return entity =&gt; entity.PackungUpdates.AsQueryable().Any(func); } 那样添加 AsQueryable() 看起来它会起作用但我不知道那是多么“邪恶”......我不想改变 PackungsUpdates ICollection 到 IQueryable,因为我不想将 IQueryable 暴露给更高层......
  • @Maarten 我没有理解您的评论是什么意思。如果我将 entity =&gt; entity.PackungUpdates.Any(func) 存储到 Expression 变量中,这不会改变 Any(func)- 部分的编译器错误...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-21
  • 2021-08-09
  • 1970-01-01
  • 2020-09-10
  • 2021-12-10
  • 2021-08-22
相关资源
最近更新 更多