Last active
July 29, 2024 13:03
-
-
Save nphmuller/05ff66dfa67e1d02cdefcd785661a34d to your computer and use it in GitHub Desktop.
CombineQueryFilers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyDbContext : DbContext | |
{ | |
private ITenantIdLocator tenantIdLocator; | |
public MyDbContext(ITenantIdLocator tenantIdLocator) | |
{ | |
if (tenantIdLocator == null) throw new ArgumentNullException(nameof(tenantIdLocator)); | |
this.tenantId = tenantIdLocator.GetTenantId(); | |
} | |
// Leave these as fields, because else the filters won't work correctly. | |
// If you want to change them, make a method which changes the value. | |
private bool filtersDisabled = false; | |
private bool tenantFilterEnabled = true; | |
private bool softDeleteFilterEnabled = true; | |
private int tenantId = 0; | |
protected override void OnModelCreating(ModelBuilder modelBuilder) | |
{ | |
base.OnModelCreating(modelBuilder); | |
ApplyQueryFilters(modelBuilder); | |
} | |
private void ApplyQueryFilters(ModelBuilder modelBuilder) | |
{ | |
var clrTypes = modelBuilder.Model.GetEntityTypes().Select(et => et.ClrType).ToList(); | |
var baseFilter = (Expression<Func<IEntity, bool>>) (_ => filtersDisabled); | |
var tenantFilter = (Expression<Func<IMultiTenantEntity, bool>>) (e => !tenantFilterEnabled || e.TenantId == tenantId); | |
var softDeleteFilter = (Expression<Func<ISoftDeletableEntity, bool>>) (e => !softDeleteFilterEnabled || e.IsDeleted == false); | |
foreach (var type in clrTypes) | |
{ | |
var filters = new List<LambdaExpression>(); | |
if (typeof(IMultiTenantEntity).IsAssignableFrom(type)) | |
filters.Add(tenantFilter); | |
if (typeof(ISoftDeletableEntity).IsAssignableFrom(type)) | |
filters.Add(softDeleteFilter); | |
var queryFilter = CombineQueryFilters(type, baseFilter, filters); | |
modelBuilder.Entity(type).HasQueryFilter(queryFilter); | |
} | |
} | |
// EFCore currently has 2 limitations: | |
// | |
// - In Expression<Func<TEntity, bool>>, TEntity has to be the final entity type and cannot | |
// be, for example, the interface type. To work around it, we change the type in the expression | |
// with ReplacingExpressionVisitor. See: https://github.com/aspnet/EntityFrameworkCore/issues/10257 | |
// | |
// - Only 1 HasQueryFilter() call is supported per entity type. The last one will overwrite each call that | |
// came before it. To work around this, we combine the multiple query filters in a single expression. | |
// See: https://github.com/aspnet/EntityFrameworkCore/issues/10275 | |
private LambdaExpression CombineQueryFilters(Type entityType, LambdaExpression baseFilter, IEnumerable<LambdaExpression> andAlsoExpressions) | |
{ | |
var newParam = Expression.Parameter(entityType); | |
var andAlsoExprBase = (Expression<Func<IEntity, bool>>) (_ => true); | |
var andAlsoExpr = ReplacingExpressionVisitor.Replace(andAlsoExprBase.Parameters.Single(), newParam, andAlsoExprBase.Body); | |
foreach (var expressionBase in andAlsoExpressions) | |
{ | |
var expression = ReplacingExpressionVisitor.Replace(expressionBase.Parameters.Single(), newParam, expressionBase.Body); | |
andAlsoExpr = Expression.AndAlso(andAlsoExpr, expression); | |
} | |
var baseExp = ReplacingExpressionVisitor.Replace(baseFilter.Parameters.Single(), newParam, baseFilter.Body); | |
var exp = Expression.OrElse(baseExp, andAlsoExpr); | |
return Expression.Lambda(exp, newParam); | |
} | |
} |
I usually did something like this:
public void MyMethod()
{
myContext.DisableFilterOne();
try
{
myContext.MyEntities.ToList(); // Replace by business logic that accesses db context.
}
finally
{
myContext.EnableFilterOne();
}
}
This ensures that the filter can not accidentally be left off if an exception occurs. And since DbContext is not thread safe it should work in most scenarios just fine.
@nphmuller humm, how about just add the IgnoreQueryFilters to processing at the moment.
And i wonder the way we mannually disabled filter working in async/await funtion might be not well done.
@phamvietdung Doesn’t that disable all query filters? My goal was to disable just one of the query filters.
@nphmuller oh well yes. :D
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for your awesome workaround to solve the issue of multiple filters with interfaces
I am wondering if there is nay way I could disable one of the filters for one read operation only and it get back to work again?