我尝试使用全局查询过滤器在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。
答案 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
重新搭建脚手架时,无需进入并编辑任何生成的类。