代码之家  ›  专栏  ›  技术社区  ›  Martin Brandl

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

  •  1
  • Martin Brandl  · 技术社区  · 8 年前

    我试着用 在ASP.NET核心web应用程序中实现多租户。目前,我为每个租户都有一个单独的数据库,并在startup.cs中配置上下文,如下所示:

    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);
    

    这个很好用。现在,客户不再需要为每个租户提供单独的数据库,所以我添加了一个 teanntId 列,并希望利用全局查询过滤器来实现这一点。


    documentation ,我可以在 OnModelCreating 方法:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("
        modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
    }
    

    有没有其他方法可以像使用 DbContextOptionsBuilder ?

    1 回复  |  直到 8 年前
        1
  •  1
  •   Martin Brandl    7 年前

    我最终使用了一个分部类来重写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);
        }
    }
    

    我仍然需要修改生成的代码(将生成的OnModelCreating signature更改为OnModelCreatingInternal并删除覆盖)。但至少我得到了一个编译器错误,所以我不能忘记它。

        2
  •  1
  •   yanbu    6 年前

    我希望能够自动过滤表中名为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

    只需在启动时将其添加到依赖项注入,而不是EF生成的上下文: serivces.AddDbContext<Filtered_Db_Context>()

    当您重新构建scaffold时,不需要进入并编辑任何生成的类。