首页 > 解决方案 > 在 ApplicationDbContext 中的 OnModelCreating 中的 ApplicationUser 上的 Queryfilter 创建 StackOverflowException

问题描述

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

但是,将查询过滤器添加到 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 部分。

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

标签: c#asp.net-coreentity-framework-coreasp.net-identityasp.net-core-2.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);            
    }       
}

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


推荐阅读