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?
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.