Dynamic query filter expression in include

asp.net-core c# entity-framework entity-framework-core lambda

Question

i am working with the entity framework core HasQueryFilter method while having a dynamic variable within this filter expression. Because of this dynamic parameter (lets name it "MinPriority") i cannot directly pass a lambda expression like

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

as filter, as it compiles and ignores any changes to MinPriority. Thats why I generate a new func on every call like:

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

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

this is working fine for

dbContext.GroupItems.ToList()

i can also change the MinPriority and the change to this variable is regionized by the filter.

but when calling this entity as an include an exception is thrown by EF core:

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

throws 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]()

i created a simple console application which produces this exception: https://gist.github.com/cyptus/f9ac8bb74b2a7d98d148326502600d40

is there any other way to provide a query filter to an EF core DbSet with an dynamic variable?

1
1
10/16/2018 6:55:30 PM

Accepted Answer

It's definitely possible.

The requirement is that the dynamic part must originate from instance member (field, property, method) of the context class.

Which you sort of did. The problem is that the Func (and basically any method receiving an entity and returning bool) cannot be translated to SQL and requires client evaluation, which apparently currently doesn't work when applied to Include.

Even if it worked, it's always better to use SQL translatable (server evaluation) expressions. In your example it will be sufficient to provide context instance accessor to the static Program.MinPriority and use it inside the global filter definition:

public class DynamicQueryDbContext : AppDbContext
{
    protected int MinPriority => Program.MinPriority;

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

It's sort of explained in the Global Query Filters documentation, but only as a Tip to the encompassing example:

Note the use of a DbContext instance level field: _tenantId used to set the current tenant. Model-level filters will use the value from the correct context instance (that is, the instance that is executing the query).

What I wish to see there is clear explanation of the dynamic filter requirements and limitations.

3
10/17/2018 5:25:04 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow