3

In my application there is a Enum which name is RecordType and all of tables contains a field named 'TypeId'. When a user Add a new record, I set TypeId based on user's TypeId. in this way I want to load data realate to each user's Type.

RecordType is :

public enum RecordType
{
    None=0,
    
    Programmers = 1,
    
    Marketer = 3,
    
    Financial = 5,
}

When a user log-in to the system with type of Programmer I must just load all the data that have added by users with type 'Programmer'.

I wanted to use HasQueryFilter() but As I know it just work with static fields and can not use currentUserId beacuse it is possible after running application.

I added an extension method like this:

public static class QueryFilterExtensions
{
    public static IQueryable<TEntity> FilterByUser<TEntity>(this IQueryable<TEntity> query, ICurrentUserService currentUser) where TEntity : BaseEntity
    {
        if (currentUser.TypeId != Domain.Enums.RecordType.None)
            query = query.Where(e => e.TypeId == currentUser.TypeId);
        return query;
    }
}

in this way I have to reapt calling this Extension method like below in all part of my reading.

 return await _dbContext.Groups.OrderBy(x => x.Id)
             .FilterByUser(_currentUser)
                    .ProjectTo<GroupDto>(_mapper.ConfigurationProvider)
                    .PaginatedListAsync(request.PageIndex, request.PageSize);

3 Answers 3

2

I wanted to use HasQueryFilter() but As I know it just work with static fields and can not use currentUserId beacuse it is possible after running application.

Actually it is possible, but poorly documented - just as a tip to Global Query Filters documentation topic 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 it really means and should have been documented is that the global filter query expression parts which are not coming from a parameter or are not constants must be rooted at the context class in order to be dynamic. Or in other words, must originate from property/field/method of the context class.

For instance, if your context has property

public RecordType CurrentUserTypeId => // coming from injected service or something

Then you can use inside a global query filter and it will evaluate dynamically for each context. You can set it up generically using something like this inside your context class


void SetQueryFilter<TEntity>(ModelBuilder modelBuilder)
    where TEntity : BaseEntity
{
    modelBuilder.Entity<TEntity>().HasQueryFilter(
        e => this.CurrentUserTypeId == RecordType.None || e.TypeId == this.CurrentUserTypeId);
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    // other stuff...
    var setQueryFilterMethod = new Action<ModelBuilder>(SetQueryFilter<MyBaseEntity>)
        .Method.GetGenericMethodDefinition();
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        if (entityType.BaseType == null && typeof(BaseEntity).IsAssignableFrom(entityType.ClrType))
        {
            setQueryFilterMethod
                .MakeGenericMethod(entityType.ClrType)
                .Invoke(this, new object[] { modelBuilder });
        }
    }
}

Sign up to request clarification or add additional context in comments.

Comments

0

In addition to Ivan's answer, this is also allowed:

MyDbContext myDbContext = null!;
builder.HasQueryFilter(e => e.Foo == myDbContext.Bar);

Since HasQueryFilter accepts an Expression, the definition-time value (null) of myDbContext will not be captured, it will be substituted by EF for the DbContext instance that is executing the query.

This approach enables you to define query filters outside of the DbContext lexical scope and/or in static methods, which is useful if for example you're using your own entity builders and keeping the actual DbContext clean.

Comments

0

I had the same problem, although mine was a bit different as my models doesn't inherit from a base class as I'm using reverse engineering (scaffold). so the solution doesn't work for me. The only way was to use Expression, but it's constant and won't update for each request. The solution is to use Custom Reverse Engineering Templates. Editing DbContext.t4 file and adding some lines of code, solved the problem:

// ...
#>
    entity.Property(e => e.<#= property.Name #>)<#= code.Fragment(propertyFluentApiCalls, indent: 4) #>;
<#

// added code:
if(property.Name == "TypeId")
{
    #>
        entity.HasQueryFilter(e => e.TypeId == currentUserId);
    <#
}

// ...

This will work dynamically and uses different value for each request.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.