如何在数据库优先方法中配置全局查询过滤器?
如何在数据库优先方法中配置全局查询过滤器?
我试图在ASP.NET Core web应用程序中使用全局查询过滤器实现多租户功能。目前,我为每个租户都有一个单独的数据库,并在startup.cs中配置上下文,如下所示:
services.AddDbContext((service, options) => options.UseSqlServer(Configuration[$"Tenant:{service.GetService ().Current}:Database"]) .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)), contextLifetime: ServiceLifetime.Scoped, optionsLifetime: ServiceLifetime.Scoped);
这个方法很好用。现在客户不再需要每个租户单独的数据库,所以我在每个表中添加了一个teanntId
列,并希望利用全局查询过滤器来实现这一点。
如文档中所述,我可以在OnModelCreating
方法中添加查询过滤器:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().Property ("TenantId").HasField(" modelBuilder.Entity ().HasQueryFilter(b => EF.Property (b, "TenantId") == _tenantId); }
但我使用的是数据库优先方法,每次生成模型时都会丢失该配置。是否有其他方法可以配置全局查询过滤器,例如使用DbContextOptionsBuilder
?
我正在使用EF Core 2.1.2。
如何在数据库优先方法中配置全局查询过滤器?
在使用谷歌搜索这个主题时,这是第一条出现的信息,所以我在一段时间之后想到了一个更全面、更容易使用的解决方案。我想要能够自动过滤所有生成的实体,这些实体在表中具有名为TenantID的列,并在保存时自动插入已登录用户的TenantID。
示例部分类:
public partial class Filtered_Db_Context : MyDbContext { private int _tenant; public Filtered_Db_Context(IHttpContextAccessor context) : base() { _tenant = AuthenticationMethods.GetTenantId(context?.HttpContext); } public Filtered_Db_Context(HttpContext context) : base() { _tenant = AuthenticationMethods.GetTenantId(context); } public void AddTenantFilter<T>(ModelBuilder mb) where T : class { mb.Entity<T>().HasQueryFilter(t => EF.Property<int>(t, "TenantId") == _tenant); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //For any entity that has a TenantId it will only allow logged in user to see data from their own Tenant foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var prop = entityType.FindProperty("TenantId"); if (prop != null && prop.ClrType == typeof(int)) { GetType() .GetMethod(nameof(AddTenantFilter)) .MakeGenericMethod(entityType.ClrType) .Invoke(this, new object[] { modelBuilder }); } } } public override int SaveChanges(bool acceptAllChangesOnSuccess) { InsertTenantId(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override int SaveChanges() { InsertTenantId(); return base.SaveChanges(); } public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { InsertTenantId(); return base.SaveChangesAsync(cancellationToken); } public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) { InsertTenantId(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void InsertTenantId() { if (_tenant != 0) { var insertedOrUpdated = ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified).ToList(); insertedOrUpdated.ForEach(e => { var prop = e.Property("TenantId"); int propIntVal; bool isIntVal = int.TryParse(prop.CurrentValue.ToString(), out propIntVal); if (prop != null && prop.Metadata.IsForeignKey() && isIntVal && propIntVal != _tenant) { prop.CurrentValue = _tenant; } }); } } }
现在,您可以像正常使用Filtered_Db_Context
类一样执行所有的Entity Framework操作,而租户函数则在查询和保存时处理,而不需要考虑它。
只需将其添加到启动时的依赖注入中,而不是您生成的EF上下文中:serivces.AddDbContext<Filtered_Db_Context>()
当重新生成时,无需编辑任何生成的类。
在使用数据库优先的方法配置全局查询过滤器时,可能会遇到以下问题:
问题的原因:
- 在使用数据库优先的方法配置全局查询过滤器时,需要在生成的代码中修改OnModelCreating方法,但容易忘记修改或遗漏修改。
解决方法:
- 可以使用部分类(partial class)来覆盖OnModelCreating方法,以确保在生成的代码中正确配置全局查询过滤器。
- 在MyContext类中创建一个部分类MyContext,继承自DbContext,并重写OnModelCreating方法。
- 在重写的OnModelCreating方法中,调用OnModelCreatingInternal方法(自定义方法),然后配置全局查询过滤器。
- 具体的代码如下:
public partial class MyContext : DbContext
{
public MyContext(DbContextOptions
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
OnModelCreatingInternal(modelBuilder);
modelBuilder.Entity
modelBuilder.Entity
}
}
- 还需要修改生成的代码(将生成的OnModelCreating签名更改为OnModelCreatingInternal,并移除override关键字),以确保正确调用重写的OnModelCreating方法。
- 在EF Core 3.1中,可以使用部分方法(partial method)来进一步简化配置,具体代码如下:
partial void OnModelCreatingPartial(ModelBuilder modelBuilder) { }