How to make conditional query filters in EF Core?
Asked Answered
D

1

6

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.

Dorcasdorcea answered 28/4, 2020 at 22:34 Comment(1)
Have you found out the solution? I'm getting same issue, whenever it checks HasValue, it's stuck at the initialized value, value set from the first call.Gordie
S
0

I don't think your problem is how you handle the global filter query but how you execute your background jobs.

The TenantId and any other information dependent of the logged user will not be available to hangfire jobs.

You could easily solve it by passing the appropriate parameters to your background jobs, this way hangfire will serialize it in the DB and deserialize it when it runs.

private long currentTenantId = loggedUser.GetTenantId(); // or any other way to get the tenantId when the background job was scheduled.
BackgroundJob.Enqueue<ISomeService>(s => s.SomeFunction(currentTenantId));

If you look at the hangfire Job table you will notice that the InvocationData and Arguments columns will hold all the information needed to run SomeFunction().

Sy answered 27/11, 2023 at 19:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.