Gup*_*R4c 7 c# caching multi-tenant entity-framework-core asp.net-core
我正在构建一个多租户应用程序,并且遇到了我认为EF Core在请求中缓存租户ID的困难.唯一似乎有帮助的是在我登录和退出租户时不断重建应用程序.
我认为它可能与IHttpContextAccessor实例是单例有关,但它不能作为范围,当我在没有重建的情况下登录和退出时,我可以看到租户的名称在页面顶部发生变化,所以它不是问题.
我能想到的另一件事是EF Core正在进行某种查询缓存.我不确定为什么它会考虑它是一个范围的实例,它应该在每个请求上重建,除非我错了,我可能是.我希望它的行为类似于作用域实例,因此我可以在模型构建时在每个实例上简单地注入租户ID.
如果有人能指出我正确的方向,我真的很感激.这是我目前的代码:
TenantProvider.cs
public sealed class TenantProvider :
ITenantProvider {
private readonly IHttpContextAccessor _accessor;
public TenantProvider(
IHttpContextAccessor accessor) {
_accessor = accessor;
}
public int GetId() {
return _accessor.HttpContext.User.GetTenantId();
}
}
Run Code Online (Sandbox Code Playgroud)
...注入TenantEntityConfigurationBase.cs,我用它来设置全局查询过滤器.
internal abstract class TenantEntityConfigurationBase<TEntity, TKey> :
EntityConfigurationBase<TEntity, TKey>
where TEntity : TenantEntityBase<TKey>
where TKey : IEquatable<TKey> {
protected readonly ITenantProvider TenantProvider;
protected TenantEntityConfigurationBase(
string table,
string schema,
ITenantProvider tenantProvider) :
base(table, schema) {
TenantProvider = tenantProvider;
}
protected override void ConfigureFilters(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureFilters(builder);
builder.HasQueryFilter(
e => e.TenantId == TenantProvider.GetId());
}
protected override void ConfigureRelationships(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureRelationships(builder);
builder.HasOne(
t => t.Tenant).WithMany().HasForeignKey(
k => k.TenantId);
}
}
Run Code Online (Sandbox Code Playgroud)
...然后由所有其他租户实体配置继承.不幸的是,它似乎没有按照我的计划运作.
我已经验证了用户主体返回的租户ID正在根据租户用户的登录情况而变化,因此这不是问题.在此先感谢您的帮助!
更新
有关使用EF Core 2.0.1+的解决方案,请查看我未接受的答案.
更新2
另请参阅Ivan的2.0.1+更新,它代表DbContext的过滤器表达式,它恢复了在基本配置类中定义一次的能力.这两种解决方案各有利弊.我再次选择了Ivan,因为我只想尽可能地利用我的基本配置.
目前(从EF Core 2.0.0开始),动态全局查询过滤非常有限.仅当动态部分由目标 派生类(或其基本派生类之一)的直接属性提供时,它才有效.正如在文档中的模型级查询过滤器示例中一样.正是这样 - 没有方法调用,没有嵌套属性访问器 - 只是上下文的属性.有点在链接中解释:DbContextDbContext
请注意
DbContext实例级属性的使用:TenantId.模型级过滤器将使用来自正确上下文实例的值.即执行查询的那个.
要使它在您的场景中工作,您必须创建一个这样的基类:
public abstract class TenantDbContext : DbContext
{
protected ITenantProvider TenantProvider;
internal int TenantId => TenantProvider.GetId();
}
Run Code Online (Sandbox Code Playgroud)
从它派生你的上下文类,并以某种方式将TenantProvider实例注入其中.然后修改TenantEntityConfigurationBase类以接收TenantDbContext:
internal abstract class TenantEntityConfigurationBase<TEntity, TKey> :
EntityConfigurationBase<TEntity, TKey>
where TEntity : TenantEntityBase<TKey>
where TKey : IEquatable<TKey> {
protected readonly TenantDbContext Context;
protected TenantEntityConfigurationBase(
string table,
string schema,
TenantDbContext context) :
base(table, schema) {
Context = context;
}
protected override void ConfigureFilters(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureFilters(builder);
builder.HasQueryFilter(
e => e.TenantId == Context.TenantId);
}
protected override void ConfigureRelationships(
EntityTypeBuilder<TEntity> builder) {
base.ConfigureRelationships(builder);
builder.HasOne(
t => t.Tenant).WithMany().HasForeignKey(
k => k.TenantId);
}
}
Run Code Online (Sandbox Code Playgroud)
一切都会按预期工作.请记住,Context变量类型必须是DbContext派生类 - 用接口替换它将不起作用.
2.0.1的更新:正如@Smit在评论中指出的那样,v2.0.1删除了大部分限制 - 现在你可以使用方法和子属性了.
但是,它提出了另一个要求-动态表达必须要扎根在DbContext.
这个要求打破了上述解决方案,因为表达式root是TenantEntityConfigurationBase<TEntity, TKey>类,并且DbContext由于缺乏编译时间支持来生成常量表达式,因此在外部创建这样的表达式并不容易.
它可以通过一些低级表达式操作方法来解决,但在您的情况下更容易将过滤器创建移动到通用实例方法中TenantDbContext并从实体配置类中调用它.
以下是修改:
TenantDbContext类:
internal Expression<Func<TEntity, bool>> CreateFilter<TEntity, TKey>()
where TEntity : TenantEntityBase<TKey>
where TKey : IEquatable<TKey>
{
return e => e.TenantId == TenantId;
}
Run Code Online (Sandbox Code Playgroud)
TenantEntityConfigurationBase <TEntity,TKey>类:
builder.HasQueryFilter(Context.CreateFilter<TEntity, TKey>());
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2115 次 |
| 最近记录: |