包含中的动态查询过滤器表达式

时间:2018-10-16 18:54:06

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

我正在使用实体框架核心的HasQueryFilter方法,同时在此过滤器表达式中包含动态变量。由于存在这个动态参数(我将其命名为“ MinPriority”),因此我无法直接传递lambda表达式,例如

HasQueryFilter(x => x.Priority >= Program.MinPriority);

作为过滤器,它会编译并忽略对MinPriority的任何更改。这就是为什么我会在每个调用中生成一个新的func的原因,

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提供查询过滤器吗?

1 个答案:

答案 0 :(得分:3)

绝对有可能。

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

您做了些什么。问题是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用于设置当前租户。模型级过滤器将使用正确上下文实例(即正在执行查询的实例)中的值。

我希望看到的是动态过滤器要求和限制的清晰说明。