【问题标题】:Queryfilter on ApplicationUser in OnModelCreating in ApplicationDbContext creates StackOverflowException在 ApplicationDbContext 中的 OnModelCreating 中的 ApplicationUser 上的 Queryfilter 创建 StackOverflowException
【发布时间】:2019-12-23 12:30:55
【问题描述】:

我需要创建一个全局查询过滤器,只过滤属于某个租户的用户。

但是,将查询过滤器添加到 OnModelCreating 时,我得到了一个堆栈溢出。

我使用 IHttpContextAccessor 从当前登录的用户获取 TenantId。这适用于其他实体,但 ApplicationUser 会产生错误。这可能是循环代码的问题吗?

我的 ApplicationDbContext 如下(为了清楚起见缩写)

public class ApplicationDbContext
: IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{        
    private readonly IHttpContextAccessor _contextAccessor;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor contextAccessor)
        : base(options)
    {
        _contextAccessor = contextAccessor;
    }

    public virtual Guid? CurrentTenantId
    {
        get
        {
            return Users.FirstOrDefault(u => u.UserName == _contextAccessor.HttpContext.User.Identity.Name)?.TenantId;
        }
    }

    public virtual string CurrentUserName
    {
        get
        {
            return Users.FirstOrDefault(u => u.UserName == _contextAccessor.HttpContext.User.Identity.Name)?.UserName;
        }
    }

    public DbSet<ApplicationUser> ApplicationUser { get; set; }
    public DbSet<Tenant> Tenant { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Tenant>().HasQueryFilter(e => e.TenantId == CurrentTenantId);
        builder.Entity<ApplicationUser>().HasQueryFilter(e => e.TenantId == CurrentTenantId);            
    }       
}

}

我已将services.AddHttpContextAccessor() 添加到我启动时的 ConfifureServices 部分。

关于如何解决此问题的任何建议?

【问题讨论】:

  • 发生了什么事;您初始化 DbContext。在该上下文中,您尝试访问 Users 数据集,但要能够读取它,您必须首先初始化 DbContext,因此 .net 将尝试初始化 DbContext 类。现在你已经陷入了infinite loop。因为 DbContext 需要读取用户 Dbset 而用户 dbset 需要一个 DbContext,所以它会尝试创建一个。此问题的解决方案是在 DbContext 类之外采用检索 TenantId 的逻辑。

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


【解决方案1】:

有很多方法可以解决这个问题,但我只会给你我喜欢的方法。

首先,您必须创建 第二个 DbContext 类来访问您的用户数据库集,而这不会应用到它的查询过滤器。

public class UserDbContext : DbContext 
{
    public DbSet<ApplicationUser> Users { get; set; }  
}

您在启动时以与当前 DbContext 类相同的方式注册,并且使用相同的连接字符串。

然后您可以继续创建一个服务,该服务将为您提供您的 TenantID。

public class TenantProvider : ITenantProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly UserDbContext _userDbContext;

    private Guid _tenantId;

    public TenantProvider(UserDbContext userDbContext,
                          IHttpContextAccessor httpContextAccessor)
    {
        _userDbContext = userDbContext;
        _httpContextAccessor = httpContextAccessor;
    }

    private void SetTenantId()
    {
        if (_httpContextAccessor.HttpContext == null)
        {
            // Whatever you would like to return if there is no request (eg. on startup of app).
            _tenantId = new Guid();
            return;
        }

        _tenantId = _userDbContext.Users.FirstOrDefault(u => u.UserName == _httpContextAccessor.HttpContext.User.Identity.Name)?.TenantId;
        return;
    }

    public Guid GetTenantId()
    {
        SetTenantId();
        return _tenantId;
    }

当然还有界面

public interface ITenantProvider
{
    Guid GetTenantId();
}

您也在启动时注册此服务。

services.AddScoped<ITenantProvider, TenantProvider>();

然后你修改你的ApplicationDbContext:

public class ApplicationDbContext
: IdentityDbContext<ApplicationUser, ApplicationRole, string, IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{        
    private readonly IHttpContextAccessor _contextAccessor;

    private Guid _tenantId;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor contextAccessor, ITenantProvider _tenantProvider)
        : base(options)
    {
        _contextAccessor = contextAccessor;
        _tenantId = _tenantProvider.GetTenantId();

    }

    public DbSet<ApplicationUser> ApplicationUser { get; set; }
    public DbSet<Tenant> Tenant { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.Entity<Tenant>().HasQueryFilter(e => e.TenantId == _tenantId);
        builder.Entity<ApplicationUser>().HasQueryFilter(e => e.TenantId == _tenantId);            
    }       
}

应该就是这样,不再有无限循环:) 祝你好运

【讨论】:

  • 谢谢丹尼斯。创建另一个上下文并不是我的首要任务。这样就可以了。
猜你喜欢
  • 1970-01-01
  • 2016-07-15
  • 2016-03-15
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多