Реализации MultiTenancy и HasQueryFilter для глобальной фильтрации Entity Framework Core
Есть две основные статьи, по которым пытаюсь решить задачу:
В каждой из статей присутствует код такого вида:
public class MultitenancyContext(string tenantId) : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasQueryFilter(b => b.TenantId == tenantId);
}
}
Или
public ContactContext(DbContextOptions<ContactContext> opts, ITenantService service) : base(opts)
=> _tenant = service.Tenant;
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<MultitenantContact>().HasQueryFilter(mt => mt.Tenant == _tenant);
Пример моей реализации:
public partial class AppDbContext(DbContextOptions<AppDbContext> options, ICurrentUserContext currentUserContext) : DbContext(options)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAssemblyConfigurations();
if (currentUserContext.CompanyId is not null)
{
modelBuilder.Entity<UserEntity>().HasQueryFilter(tenant => tenant.CompanyId == currentUserContext.CompanyId);
}
}
}
В чём проблема? При запуске проекта этот код выполняется (логично). Но если я изменю текущего пользователя (войду под другим аккаунтом) это работать уже не будет для пользователя из другой компании. На сколько я изучил вопрос, OnModelCreating выполняется только раз при старте программы, даже если у меня сам DbContext зарегистрирован как Scoped сервис:
var connectionStringName = GlobalConstants.Db.ConnectionStringName;
var connectionString = configuration.GetConnectionString(connectionStringName);
services.AddDbContext<AppDbContext>(option => option.UseNpgsql(connectionString).LogTo(Console.WriteLine, LogLevel.Information));
Вопрос в том, как правильно тогда это реализовать? Я что-то упускаю?
UPD1:
Важный момент, что я не использую тип регистрации DbContext через AddDbContextPool, поскольку у меня возникают ошибка связанные с внедрением ICurrentUserContext при построении проекта.
UPD2:
Вот такой вариант не работает:
public partial class AppDbContext : DbContext
{
private readonly int? _currentCompanyId;
public AppDbContext(DbContextOptions<AppDbContext> options, ICurrentUserContext currentUserContext) : base(options)
{
_currentCompanyId = currentUserContext.CompanyId;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAssemblyConfigurations();
if (_currentCompanyId is not null)
{
modelBuilder.Entity<UserEntity>().HasQueryFilter(tenant => tenant.CompanyId == _currentCompanyId);
}
}
}
Пробовал в связке с различными типами регистрации DbContext:
services.AddDbContextFactory<TContext>(options => options.UseNpgsql(connectionString).LogTo(Console.WriteLine, LogLevel.Information));
services.AddDbContext<TContext>(option => option.UseNpgsql(connectionString).LogTo(Console.WriteLine, LogLevel.Information));
UPD3:
Следующий варианты хранения значений так же не работают:
public int? CurrentCompanyId { get; set; }
public ICurrentUserContext CurrentUserContext { get; set; }
UPD4:
На основе статьи "Чередование между несколькими моделями с одинаковым типом DbContext" я создал свой вариант кеш-фабрики:
public sealed class AppContextCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context, bool designTime)
{
AppDbContext? dbContext = context as AppDbContext;
if (dbContext is null)
{
return dbContext.GetType();
}
return (context.GetType(), dbContext.CurrentUserContext.CompanyId, designTime);
}
}
И изменил свою регистрацию DbContext:
services.AddDbContextFactory<TContext>(options => options
.UseNpgsql(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.ReplaceService<IModelCacheKeyFactory, AppContextCacheKeyFactory>()
);
При каждом обращении срабатывает строчка return (context.GetType(), dbContext.CurrentUserContext.CompanyId, designTime);, что обозначает, что создаётся и добавляется в кеш новый DbContext. Но программа так и не попадает в метод OnModelCreating. Более того, при анализе лога запроса, я так и не видел AND COMPANY_ID = ... или чего-то подобного.