我正在尝试使用单个数据库在应用程序中实现多租户。我引入了一个中间件,用于获取每个请求的 TenantId 和一个模型构建器的全局过滤器,用于按 TenantId 过滤所有相关实体。
我的问题是 TenantId =tenantAccessor.TenantId 的值不是根据 http 请求动态设置的,而是在模型创建期间应用一次默认 TenantID 值为 0 的过滤器。
public static ModelBuilder AddTenantQueryFilter(this ModelBuilder modelBuilder, ITenantAccessor tenantAccessor)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(TenantBaseEntity).IsAssignableFrom(entityType.ClrType))
{
var method = typeof(TenantsExtension)
.GetMethod(nameof(GetTenantFilter), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
.MakeGenericMethod(entityType.ClrType);
var filter = method.Invoke(null, new object[] { tenantAccessor });
entityType.SetQueryFilter((LambdaExpression)filter);
}
}
return modelBuilder;
}
private static LambdaExpression GetTenantFilter<TEntity>(ITenantAccessor tenantAccessor) where TEntity : TenantBaseEntity
{
Expression<Func<TEntity, bool>> filter = e => e.TenantId == tenantAccessor.TenantId;
return filter;
}
这是我当前的过滤器。我需要更改什么才能使我的过滤器应用/使用请求的当前 TenantId 值?
其余的实施工作。中间件被调用并为每个请求正确设置 TenantId,如果我在创建模型时手动将其设置为现有 TenantId,则过滤器也可以工作。
这是我尝试过的另一个过滤器实现,但有同样的问题:
public static ModelBuilder AddTenantQueryFilter(this ModelBuilder modelBuilder, ITenantAccessor tenantAccessor)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(TenantBaseEntity).IsAssignableFrom(entityType.ClrType))
{
var parameter = Expression.Parameter(entityType.ClrType, "e");
var property = Expression.Property(parameter, nameof(TenantBaseEntity.TenantId));
var constant = Expression.Constant(tenantAccessor.TenantId);
var body = Expression.Equal(property, constant);
var lambda = Expression.Lambda(body, parameter);
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
}
return modelBuilder;
}
您的解决方案有两个问题:
DbContext
访问的属性。OnModelCreating
实例仅调用 DbContext
方法一次,从而使您的 tenantAccessor
参数无效。让我们考虑以下
DbContext
后代的实现,它解决了这些问题:
public class MyDbContext : DbContext
{
public ITenantAccessor? TenantAccessor { get; set; }
public int? TenantId => TenantAccessor?.TenantId;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Entity configuration
modelBuilder.ApplyQueryFilter<TenantBaseEntity>(e => e.TenantId == TenantId);
}
}
在此示例中,我使用了扩展方法 ApplyQueryFilter,它简化了此类过滤器的应用。
这里,
TenantId
是DbContext
的属性,满足第一个要求。 TenantAccessor
属性可以通过依赖注入或中间件设置,因此 OnModelCreating
仅被调用一次这一事实并不重要。