【问题标题】:Dynamic query filter expression in include包含中的动态查询过滤器表达式
【发布时间】:2018-10-16 18:54:05
【问题描述】:

我正在使用实体框架核心 HasQueryFilter 方法,同时在此过滤器表达式中有一个动态变量。由于这个动态参数(让我们将其命名为“MinPriority”),我不能直接传递像

这样的 lambda 表达式
HasQueryFilter(x => x.Priority >= Program.MinPriority);

作为过滤器,因为它编译并忽略对 MinPriority 的任何更改。这就是为什么我在每次调用时都会生成一个新函数,例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<GroupItem>().HasQueryFilter(x => GenerateMyFilter()(x));
}

protected Func<GroupItem, bool> GenerateMyFilter()
{
    return x => x.Priority >= Program.MinPriority;
}

这对

来说很好用
dbContext.GroupItems.ToList()

我还可以更改 MinPriority,并且此变量的更改由过滤器区域化。

但是当将此实体作为包含调用时,EF 核心会引发异常:

dbContext.Groups.Include(x => x.Items).ToList()

抛出 NullReferenceException:

   at lambda_method(Closure , AnonymousObject )
   at System.Linq.Lookup`2.CreateForJoin(IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
   at System.Linq.Enumerable.JoinIterator[TOuter,TInner,TKey,TResult](IEnumerable`1 outer, IEnumerable`1 inner, Func`2 outerKeySelector, Func`2 innerKeySelector, Func`3 resultSelector, IEqualityComparer`1 comparer)+MoveNext()
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source, Int32& length)
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.OrderedEnumerable`1.GetEnumerator()+MoveNext()
   at System.Linq.Enumerable.SelectIPartitionIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.IncludeCollection[TEntity,TRelated,TElement](Int32 includeId, INavigation navigation, INavigation inverseNavigation, IEntityType targetEntityType, IClrCollectionAccessor clrCollectionAccessor, IClrPropertySetter inverseClrPropertySetter, Boolean tracking, TEntity entity, Func`1 relatedEntitiesFactory, Func`3 joinPredicate)
   at lambda_method(Closure , QueryContext , Group , Object[] )
   at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
   at lambda_method(Closure , Group )
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at EFCoreTest.Program.PrintGroups[TDbContext]()

我创建了一个简单的控制台应用程序,它会产生这个异常:https://gist.github.com/cyptus/f9ac8bb74b2a7d98d148326502600d40

有没有其他方法可以通过动态变量向 EF 核心 DbSet 提供查询过滤器?

【问题讨论】:

    标签: c# entity-framework asp.net-core lambda entity-framework-core


    【解决方案1】:

    这绝对是可能的。

    要求动态部分必须来源于上下文类的实例成员(字段、属性、方法)。

    你做了什么。问题是Func(以及基本上任何接收实体并返回 bool 的方法)都无法转换为 SQL,并且需要 client evaluation,这在应用于 Include 时显然目前不起作用。

    即使它有效,使用 SQL 可翻译(服务器评估)表达式总是更好。在您的示例中,为静态 Program.MinPriority 提供上下文实例访问器并在全局过滤器定义中使用它就足够了:

    public class DynamicQueryDbContext : AppDbContext
    {
        protected int MinPriority => Program.MinPriority;
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<GroupItem>()
                .HasQueryFilter(x => x.Priority >= MinPriority);
        }
    }
    

    这在Global Query Filters 文档中有所解释,但仅作为对包含example提示

    注意使用 DbContext 实例级别字段:_tenantId 用于设置当前租户。模型级过滤器将使用正确上下文实例(即执行查询的实例)中的值。

    我希望看到对动态过滤器要求和限制的清晰解释。

    【讨论】:

    • 感谢您的帮助。对于像这样的简单字段,这确实可以正常工作。一旦我将此 int 更改为例如int[] MinPriorities = new int[] { 10, 20, 30 }; 和像 x =&gt; MinPriorities.Any(minPrio =&gt; minPrio &lt;= x.Priority) 这样的过滤器停止工作。 x =&gt; x.Priority &gt; MinPriorities.Skip(1).First() 再次正常工作。这里有什么限制?有没有办法在这种类型上使用更复杂的类型/查询?
    • 正如我所提到的,它必须是可以转换为 SQL 的表达式。即您可以在Where 子句中使用并且不会引起客户评估的东西。 x =&gt; MinPriorities.Any(minPrio =&gt; minPrio &lt;= x.Priority) 绝对不可翻译 - 内存集合中仅支持原始类型的 Contains
    • 所有这些东西都没有记录,所以现在的过程是反复试验。
    • 谢谢,我也在github上开了一个问题:github.com/aspnet/EntityFrameworkCore/issues/13647
    • 目前是否有其他方法可以在 ef core 中实现这种过滤?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多