According to the Global Query Filters documentation we can't use multiple query filters for an entity, we can only have one, with multiple conditions in it:
It is currently not possible to define multiple query filters on the same entity - only the last one will be applied. However, you can define a single filter with multiple conditions using the logical AND operator (&& in C#).
So, in a project I'm working on with EF Core 3.1, I tried to do just that. In my query filter I try to apply a condition for a TenantId
and a condition for IsDeleted
:
internal Expression<Func<TEntity, bool>> QueryFilters<TEntity>()
where TEntity : class {
return _ => (TenantId.HasValue
&& _ is ITenantableEntity
&& ((ITenantableEntity)_).TenantId == new TenantId(TenantId.Value)
|| true)
&& (_ is IDeletableEntity
&& !((IDeletableEntity)_).IsDeleted
|| true);
}
The TenantId
is defined as a property on the DbContext
:
internal int? TenantId => _identity.TenantId;
The problem I'm running into is that in certain scenarios, such as Hangfire background jobs, the TenantId
will always be null. I thought I was guarding for that in the above condition, but when I tried it out, the following query parameters were generated:
queryContext.AddParameter(
name: "__ef_filter__HasValue_0",
value: (object)Invoke(queryContext => Convert(Convert(queryContext.Context, DbContext).TenantId.HasValue, Object), queryContext)
);
queryContext.AddParameter(
name: "__ef_filter__p_1",
value: (object)Invoke(queryContext => Convert(Convert(new TenantId(Convert(queryContext.Context, DbContext).TenantId.Value), Nullable`1), Object), queryContext)
);
And because of those parameters, the queries are simply crashing with a NRE. Is there a better or more appropirate way to build up the expression in the query filter so that if the TenantId
does have a value, the parameter is generated, and not otherwise without the nested conditional I've got going?
To make the query in the background job succeed, I have to use IgnoreQueryFilters()
, which fixes the TenantId
check, but now also ignores the IsDeleted
check which I'd like to continue to have.