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

时间:2018-10-01 14:00:22

标签: c# asp.net-core .net-core entity-framework-core

我尝试使用全局查询过滤器在ASP.NET Core Web应用程序中实现多租户。目前,我为每个租户都有一个单独的数据库,并在startup.cs中配置上下文,如下所示:

const x_train = tf.tensor([[80], [86], [10], [1], [2]]);
const y_train = tf.tensor([[1],[1],[1],[0],[0]])
const x_val = tf.tensor([[1], [3], [102], [100], [104]]);
const y_val = tf.tensor([[0],[0],[1],[1],[1]])
const model = tf.sequential();


model.add(tf.layers.embedding({ inputDim: dictionary.size, inputLength: max_review_length, outputDim: 1 }))
model.add(tf.layers.lstm({units: 200, dropout: 0.2, recurrentDropout: 0.2}))
model.add(tf.layers.dense({units: 1, activation:'sigmoid'}))
model.compile({ loss:'binaryCrossentropy', optimizer:'rmsprop', metrics:['accuracy'] }) 
const history=model.fit(x_train, y_train,{epochs: 12, batchSize: 5}) 
history.then(hist => console.log(hist.history.loss)) // Show error loss vs epoch

const predictOut =  model.predict(tf.tensor2d([10]))

这很好。现在,客户不再需要每个租户一个单独的数据库,因此我向每个表添加了一个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); 列,并希望利用全局查询过滤器来实现这一点。


documentation中所述,我可以在teanntId方法中添加查询过滤器:

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

我正在使用EF Core 2.1.2。

2 个答案:

答案 0 :(得分:1)

我最终使用了覆盖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符号饱和度更改为OnModelCreatingInternal并删除覆盖)。但是我至少遇到了编译器错误,所以我不能忘记它。

答案 1 :(得分:1)

这是搜索该主题时在Google上弹出的第一件事,因此我发布了一个更全面,更易于使用的解决方案,该解决方案是我敲了一段时间后才想到的。

我希望能够自动过滤所有在表中有名为TenantID的列的生成的实体,并在保存时自动插入登录用户的TenantID。

部分类示例:

dino@DINO:~/code/mplfinance$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

dino@DINO:~/code/mplfinance$ uname -a
Linux DINO 4.4.0-18362-Microsoft #476-Microsoft Fri Nov 01 16:53:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux

dino@DINO:~/code/mplfinance$ python --version
Python 3.7.4

现在,您可以使用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; } }); } } } 类像平常一样执行所有实体框架操作,并且无需在查询和保存时都考虑租户函数。

只需将其添加到Startup中的依赖项注入中,而不是EF生成的上下文中即可: Filtered_Db_Context

重新搭建脚手架时,无需进入并编辑任何生成的类。