C# 如何在数据库优先方法中配置全局查询筛选器?
我尝试使用全局查询过滤器在ASP.NET核心web应用程序中实现多租户。目前,我为每个租户都有一个单独的数据库,并在startup.cs中配置上下文,如下所示:C# 如何在数据库优先方法中配置全局查询筛选器?,c#,asp.net-core,.net-core,entity-framework-core,C#,Asp.net Core,.net Core,Entity Framework Core,我尝试使用全局查询过滤器在ASP.NET核心web应用程序中实现多租户。目前,我为每个租户都有一个单独的数据库,并在startup.cs中配置上下文,如下所示: services.AddDbContext<dbcontext>((service, options) => options.UseSqlServer(Configuration[$"Tenant:{service.GetService<ITenantProvider>().
services.AddDbContext<dbcontext>((service, options) =>
options.UseSqlServer(Configuration[$"Tenant:{service.GetService<ITenantProvider>().Current}:Database"])
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)),
contextLifetime: ServiceLifetime.Scoped, optionsLifetime: ServiceLifetime.Scoped);
但是我使用的是数据库优先的方法,所以每次生成模型时,我都会丢失配置是否有其他方法来配置全局查询筛选器,如使用DbContextOptionsBuilder
我使用的是EF Core 2.1.2。我最终使用了一个覆盖OnModelCreating方法的分部类:
public partial class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
OnModelCreatingInternal(modelBuilder);
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
}
}
公共部分类MyContext:DbContext
{
公共MyContext(DbContextOptions)
:基本(选项)
{
}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
OnModelCreatingInternal(modelBuilder);
modelBuilder.Entity().Property(“TenantId”).HasField(“
modelBuilder.Entity();
}
}
我仍然需要修改生成的代码(将生成的OnModelCreating签名更改为OnModelCreatingInternal并删除覆盖).但至少我遇到了一个编译器错误,所以我不能忘记它。这是搜索这个主题时在谷歌上弹出的第一个东西,所以我发布了一个更全面、更易于使用的解决方案,这是我经过一段时间的反复思考后想到的 我希望能够自动筛选表中有一列名为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;
}
});
}
}
}
public分部类过滤的\u Db\u上下文:MyDbContext
{
私人国际租户;
公共筛选的_Db_上下文(IHttpContextAccessor上下文):base()
{
_租户=AuthenticationMethods.GetTenantId(context?.HttpContext);
}
公共筛选的\u Db\u上下文(HttpContext上下文):base()
{
_租户=AuthenticationMethods.GetTenantId(上下文);
}
public void AddTenantFilter(ModelBuilder mb),其中T:class
{
mb.Entity().HasQueryFilter(t=>EF.Property(t,“TenantId”)=\u tenant);
}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
基于模型创建(modelBuilder);
//对于任何拥有TenantId的实体,它将只允许登录用户查看其自身租户的数据
foreach(modelBuilder.Model.GetEntityTypes()中的var entityType)
{
var prop=entityType.FindProperty(“租户”);
if(prop!=null&&prop.ClrType==typeof(int))
{
GetType()
.GetMethod(名称(AddTenantFilter))
.MakeGenericMethod(entityType.ClrType)
.Invoke(这个新对象[]{modelBuilder});
}
}
}
公共覆盖int SaveChanges(bool acceptAllChangesOnSuccess)
{
inserttenatid();
返回base.SaveChanges(AcceptalChangesOnSuccess);
}
公共覆盖int SaveChanges()
{
inserttenatid();
返回base.SaveChanges();
}
公共覆盖任务SaveChangesSync(CancellationToken CancellationToken=默认值)
{
inserttenatid();
返回base.saveChangesSync(cancellationToken);
}
public override Task savechangesync(bool acceptillchangesonsuccess,CancellationToken CancellationToken=default(CancellationToken))
{
inserttenatid();
return base.saveChangesSync(acceptAllChangesOnSuccess,cancellationToken);
}
私有void InsertTenantId()
{
如果(_租户!=0)
{
var insertedOrUpdated=ChangeTracker.Entries();
insertedOrUpdated.ForEach(e=>{
var prop=房地产(“承租人”);
int-propantval;
bool isIntVal=int.TryParse(prop.CurrentValue.ToString(),out-propIntVal);
if(prop!=null&&prop.Metadata.IsForeignKey()&&isIntVal&&propIntVal!=\u租户)
{
prop.CurrentValue=\u租户;
}
});
}
}
}
现在,您可以使用Filtered\u Db\u Context
类执行所有实体框架操作,而无需在查询和保存时考虑租户函数
只需在启动时将其添加到依赖项注入,而不是EF生成的上下文:
serivces.AddDbContext()
重新构建scaffold时,无需进入并编辑任何生成的类。您是否尝试过使用DbContext的分部类来覆盖OnModelCreating方法?@H.Herzl这是个好主意,但我仍然需要不断修改生成的代码:(@MartinBrandl)你有什么进展吗?除了像@Simon_Weaver这样修改代码之外,我使用了H.Herzl提到的解决方法。我回答了我的问题。
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;
}
});
}
}
}