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);
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 });
}
}
}