从 BackgroundService 创建时配置 DbContext 租户

Configure DbContext tenant when creating it from BackgroundService

我有一个 ASP.NET Core 3.1 应用程序,它使用现有的 Postgres 数据库,其中每个租户都存储在单独的模式中。这在 HTTP 请求中运行良好,租户标识符存储在请求中,在我的 DbContext 中,我在 OnConfiguring 方法中有以下代码:

if (this._httpContextAccessor.HttpContext?.Items["tenant"] != null)
{
  string tenant = this._httpContextAccessor.HttpContext.Items["tenant"].ToString();
  builder.SearchPath = tenant;
}
else
{
  throw new InvalidOperationException("The defined tenant does not exist. Cannot create DB Context");
}

这会调整当前请求的租户的 Postgres 搜索路径。

我现在正在尝试添加也需要使用数据库的后台服务。我的后台服务使用 IServiceProvider,我尝试如下创建我的 DbContext,由于我使用的多租户实现,这显然不起作用:

using var scope = this.serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetService<MyDbContext>();

后台服务不像 HTTP 请求那样有租户,它需要自己处理这方面并生成具有不同租户的 DbContext。

我不确定如何以某种方式调整我的上下文,以便我可以创建具有不同租户的实例。或者这是否真的是我应该做的,或者是否有更好的方法来处理这个问题。我知道通过 Postgres 架构的多租户不是 ASP.NET Core 的典型案例,但这不是我现在可以改变的。

当我需要从 HTTP 请求和与特定租户无关的后台服务中使用 DbContext 时,我如何并且应该基于 Postgres 模式将 DbContext 与租户一起使用(但例如循环通过租户以执行作业每个租户)?

一种解决方案是不直接使用 HttpContext 来获取租户 ID。相反,创建一个您可以控制并注入的服务。该服务可以使用 HttpContext,但如果您告诉它,它将使用其他内容。例如:

(注意:所有这些都是手动输入的,未经测试,但应该让您了解它是如何工作的)

public interface ITenantService
{
    string OverrideTenantId { get; set; }
    string GetTenantId();
} 

public class TenantService : ITenantService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public string OverrideTenantId { get; set; }

    public TenantService(DbContextOptions options, IHttpContextAccessor httpContextAccessor) 
        : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public string GetTenantId()
    {
        if(!string.IsNullOrEmpty(OverrideTenantId))
        {
            return OverrideTenantId;
        }

        return _httpContextAccessor.HttpContext.Items["tenant"].ToString();
    }
}

调整您的 DbContext:

public class MyContext : DbContext
{
    private readonly ITenantService _tenantService;

    public MyContext(ITenantService tenantService)
    {
        _tenantService = tenantService;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var tenantId = _tenantService.GetTenantId();

        if(string.IsNullOrEmpty(tenantId))
        {
            throw new InvalidOperationException("The defined tenant does not exist...");
        }
        builder.SearchPath = tenantId;
        base.OnConfiguring(optionsBuilder);
    }
}

将服务添加到您的 DI 容器:

services.AddScoped<ITenantService, TenantService>();

并在您的后台服务中像这样使用它:

using var scope = this.serviceProvider.CreateScope();

// This will be the same instance that the DbContext receives
var tenantService = scope.ServiceProvider.GetService<ITenantService>();
tenantService.OverrideTenantId = "whatever";

var context = scope.ServiceProvider.GetService<MyDbContext>();

请注意,如果您需要切换到不同的租户,您可能需要创建一个新的 DbContext 和范围。