ef core - multi tenancy using global filters

3k Views Asked by At

OnModelCreating is called once per db context. This is a problem since the tenant Id is set per request.

How do I re-configure the global filter everytime I create an new instance of the dbcontext?

If I can't use global filter, what is the alternative way?

Update:

I needed to provide a generic filter with an expression like e => e.TenantId == _tenantId. I am using the following expression:

var p = Expression.Parameter(type, "e");
Expression.Lambda(
    Expression.Equal(
        Expression.Property(p, tenantIdProperty.PropertyInfo),
        Expression.Constant(_tenantId))
    p);

Since this is run once, _tenantId is fixed. So even if I update it, the first value is captured in the linq expression.

So my question, what is the proper way to set the right side of that equality.

3

There are 3 best solutions below

0
herme 0 On

This is fixed with the following as the right expression

Expression.MakeMemberAccess(
    Expression.Constant(this, baseDbContextType),
    baseDbContextType.GetProperty("TenantId")

I use a base class for all my db contexts. GetProperty() works as is because TenantId is public property.

0
Norcino On

With EF.Core you can actually use the following filter and syntax

protected void OnModelCreating(ModelBuilder modelBuilder)
{
    var entityConfiguration = modelBuilder.Entity<MyTenantAwareEntity>();
    entityConfiguration.ToTable("my_table")
               .HasQueryFilter(e => EF.Property<string>(e, "TenantId") == _tenantProvider.GetTenant())
    [...]

The _tenantProvider is the class responsible to get your tenant, in your case from the HttpRequest, to do it you can use HttpContextAccessor.

0
HasanGedik On

and.. If you use it with soft delete, the solution is; --for ef core 3.1

internal static void AddQueryFilter<T>(this EntityTypeBuilder 
entityTypeBuilder, Expression<Func<T, bool>> expression)
{
var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
var expressionFilter = ReplacingExpressionVisitor.Replace(
    expression.Parameters.Single(), parameterType, expression.Body);

var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter();
if (currentQueryFilter != null)
{
    var currentExpressionFilter = ReplacingExpressionVisitor.Replace(
        currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body);
    expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter);
}

var lambdaExpression = Expression.Lambda(expressionFilter, parameterType);
entityTypeBuilder.HasQueryFilter(lambdaExpression);
}

Usage:

if (typeof(ITrackSoftDelete).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackSoftDelete>(e => IsSoftDeleteFilterEnabled == false || e.IsDeleted == false);
if (typeof(ITrackTenant).IsAssignableFrom(entityType.ClrType))
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<ITrackTenant>(e => e.TenantId == MyTenantId);

Thanks to YZahringer