如何在数据库优先方法中配置全局查询过滤器?

9 浏览
0 Comments

如何在数据库优先方法中配置全局查询过滤器?

我试图在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。

0
0 Comments

如何在数据库优先方法中配置全局查询过滤器?

在使用谷歌搜索这个主题时,这是第一条出现的信息,所以我在一段时间之后想到了一个更全面、更容易使用的解决方案。我想要能够自动过滤所有生成的实体,这些实体在表中具有名为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>()

当重新生成时,无需编辑任何生成的类。

0
0 Comments

在使用数据库优先的方法配置全局查询过滤器时,可能会遇到以下问题:

问题的原因:

- 在使用数据库优先的方法配置全局查询过滤器时,需要在生成的代码中修改OnModelCreating方法,但容易忘记修改或遗漏修改。

解决方法:

- 可以使用部分类(partial class)来覆盖OnModelCreating方法,以确保在生成的代码中正确配置全局查询过滤器。

- 在MyContext类中创建一个部分类MyContext,继承自DbContext,并重写OnModelCreating方法。

- 在重写的OnModelCreating方法中,调用OnModelCreatingInternal方法(自定义方法),然后配置全局查询过滤器。

- 具体的代码如下:

public partial class MyContext : DbContext

{

public MyContext(DbContextOptions options)

: base(options)

{

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

OnModelCreatingInternal(modelBuilder);

modelBuilder.Entity().Property("TenantId").HasField("

modelBuilder.Entity().HasQueryFilter(b => EF.Property(b, "TenantId") == _tenantId);

}

}

- 还需要修改生成的代码(将生成的OnModelCreating签名更改为OnModelCreatingInternal,并移除override关键字),以确保正确调用重写的OnModelCreating方法。

- 在EF Core 3.1中,可以使用部分方法(partial method)来进一步简化配置,具体代码如下:

partial void OnModelCreatingPartial(ModelBuilder modelBuilder) { }

0