EF Core 2.0.0查询过滤器正在缓存TenantId(已更新为2.0.1+) [英] EF Core 2.0.0 Query Filter is Caching TenantId (Updated for 2.0.1+)

查看:117
本文介绍了EF Core 2.0.0查询过滤器正在缓存TenantId(已更新为2.0.1+)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个多租户应用程序,并且在我认为EF Core在请求之间缓存租户ID的过程中遇到了困难.唯一有帮助的是,在我从租户登录和注销时不断重建应用程序.

I'm building a multi-tenant application, and am running into difficulties with what I think is EF Core caching the tenant id across requests. The only thing that seems to help is constantly rebuilding the application as I sign in and out of tenants.

我认为这可能与IHttpContextAccessor实例是一个单例有关,但无法确定范围,当我不重建而登录和注销时,可以在顶部看到租户的名称更改页面,所以这不是问题.

I thought it may have something to do with the IHttpContextAccessor instance being a singleton, but it can't be scoped, and when I sign in and out without rebuilding I can see the tenant's name change at the top of the page, so it's not the issue.

我唯一想到的另一件事是EF Core正在执行某种查询缓存.我不确定为什么要考虑它是一个作用域实例,并且应该在每个请求上都进行重建,除非我错了,可能是这样.我希望它的行为像作用域实例一样,因此我可以在模型构建时在每个实例上简单地注入租户ID.

The only other thing I can think of is that EF Core is doing some sort of query caching. I'm not sure why it would be considering that it's a scoped instance and it should be getting rebuild on every request, unless I'm wrong, which I probably am. I was hoping it would behave like a scoped instance so I could simply inject the tenant id at model build time on each instance.

如果有人能指出我正确的方向,我将非常感激.这是我当前的代码:

I'd really appreciate it if someone could point me in the right direction. Here's my current code:

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 中,在这里我用它来设置全局查询过滤器.

...which is injected into TenantEntityConfigurationBase.cs where I use it to setup a global query filter.

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

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

...which is then inherited by all other tenant entity configurations. Unfortunately it doesn't seem to work as I had planned.

我已经验证了由用户主体返回的租户ID会根据登录的租户用户而变化,所以这不是问题.预先感谢您的帮助!

I have verified that the tenant id being returned by the user principal is changing depending on what tenant user is logged in, so that's not the issue. Thanks in advance for any help!

更新

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

For a solution when using EF Core 2.0.1+, look at the not-accepted answer from me.

更新2

还要查看Ivan对于2.0.1+的更新,它代理DbContext中的过滤器表达式,从而恢复了在基本配置类中一次定义它的能力.两种解决方案都有其优点和缺点.我再次选择了Ivan,因为我只想尽可能多地利用我的基本配置.

Also look at Ivan's update for 2.0.1+, it proxies in the filter expression from the DbContext which restores the ability to define it once in a base configuration class. Both solutions have their pros and cons. I've opted for Ivan's again because I just want to leverage my base configurations as much as possible.

推荐答案

当前(自EF Core 2.0.0起),动态全局查询过滤非常有限.如果动态部分是由目标DbContext派生类(或其基础DbContext派生类之一)的直接属性提供的,则它仅在上起作用.完全与 模型级查询过滤器 示例.正是这种方式-没有方法调用,没有嵌套的属性访问器-仅仅是上下文的属性.链接中对此进行了解释:

Currently (as of EF Core 2.0.0) the dynamic global query filtering is quite limited. It works only if the dynamic part is provided by direct property of the target DbContext derived class (or one of its base DbContext derived classes). Exactly as in the Model-level query filters example from the documentation. Exactly that way - no method calls, no nested property accessors - just property of the context. It's sort of explained in the link:

请注意使用DbContext实例级别属性:TenantId.模型级过滤器将使用正确上下文实例中的值.即正在执行查询的那个.

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.

要使其在您的方案中起作用,您必须创建一个如下的基类:

To make it work in your scenario, you have to create a base class like this:

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

从中派生您的上下文类,并以某种方式将TenantProvider实例注入其中.然后修改TenantEntityConfigurationBase类以接收TenantDbContext:

derive your context class from it and somehow inject the TenantProvider instance into it. Then modify the TenantEntityConfigurationBase class to receive 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派生的 class -用 interface 替换它是行不通的.

and everything will work as expected. And remember, the Context variable type must be a DbContext derived class - replacing it with interface won't work.

更新2.0.1 :正如@Smit在注释中指出的那样,v2.0.1消除了大多数限制-现在您可以使用方法和子属性.

Update for 2.0.1: As @Smit pointed out in the comments, v2.0.1 removed most of the limitations - now you can use methods and sub properties.

但是,它引入了另一个要求-动态表达式必须必须植根DbContext.

However, it introduced another requirement - the dynamic expression must be rooted at the DbContext.

此要求打破了上述解决方案,因为表达式根是TenantEntityConfigurationBase<TEntity, TKey>类,并且由于缺乏生成常量表达式的编译时间支持,在DbContext之外创建这样的表达式并不容易.

This requirement breaks the above solution, since the expression root is TenantEntityConfigurationBase<TEntity, TKey> class, and it's not so easy to create such expression outside the DbContext due to lack of compile time support for generating constant expressions.

可以使用一些低级的表达式操作方法来解决它,但是在您的情况下,更简单的方法是将过滤器的创建移到TenantDbContext通用实例方法中,然后从实体中调用配置类.

It could be solved with some low level expression manipulation methods, but the easier in your case would be to move the filter creation in generic instance method of the TenantDbContext and call it from the entity configuration class.

以下是修改内容:

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

这篇关于EF Core 2.0.0查询过滤器正在缓存TenantId(已更新为2.0.1+)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆