编程语言
首页 > 编程语言> > c# – EF Core 2.0.0查询过滤器正在缓存TenantId(针对2.0.1进行了更新)

c# – EF Core 2.0.0查询过滤器正在缓存TenantId(针对2.0.1进行了更新)

作者:互联网

我正在构建一个多租户应用程序,并且遇到了我认为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();
    }
}

…注入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);
    }
}

…然后由所有其他租户实体配置继承.不幸的是,它似乎没有按照我的计划运作.

我已经验证了用户主体返回的租户ID正在根据租户用户的登录情况而变化,因此这不是问题.在此先感谢您的帮助!

更新

有关使用EF Core 2.0.1的解决方案,请查看我未接受的答案.

更新2

另请参阅Ivan的2.0.1更新,它代理DbContext的过滤器表达式,它恢复了在基本配置类中定义一次的能力.这两种解决方案各有利弊.我再次选择了Ivan,因为我只想尽可能地利用我的基本配置.

解决方法:

目前(从EF Core 2.0.0开始),动态全局查询过滤非常有限.仅当动态部分由目标DbContext派生类(或其基本DbContext派生类之一)的直接属性提供时,它才有效.正如文档中的Model-level query filters示例一样.正是这样 – 没有方法调用,没有嵌套属性访问器 – 只是上下文的属性.有点在链接中解释:

Note the use of a DbContext instance level property: TenantId. Model-level filters will use the value from the correct context instance. i.e. the one that is executing the query.

要使它在您的场景中工作,您必须创建一个这样的基类:

public abstract class TenantDbContext : DbContext
{
    protected ITenantProvider TenantProvider;
    internal int TenantId => TenantProvider.GetId();
}

从它派生你的上下文类,并以某种方式将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);
    }
}

一切都会按预期工作.请记住,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;
}

TenantEntityConfigurationBase< TEntity,TKey>类:

builder.HasQueryFilter(Context.CreateFilter<TEntity, TKey>());

标签:c,caching,asp-net-core,multi-tenant,entity-framework-core
来源: https://codeday.me/bug/20190611/1216663.html