从 BackgroundService 创建 DbContext 租户时配置它

Posted

技术标签:

【中文标题】从 BackgroundService 创建 DbContext 租户时配置它【英文标题】:Configure DbContext tenant when creating it from BackgroundService 【发布时间】:2021-05-01 03:00:33 【问题描述】:

我有一个 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 时,我如何以及应该如何以及应该将 DbContext 与基于 Postgres 架构的租户一起使用(但例如,循环通过租户以在每个租户上执行工作)租户)?

【问题讨论】:

不要使用HttpContext来获取租户ID,而是做一个你可以控制的服务。该服务可以使用HttpContext,但如果您告诉它,它将使用其他东西。 我认为你应该用单一模式和复合键管理多租户系统,每个表都有租户 ID 【参考方案1】:

一种解决方案是不直接使用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 和范围。

【讨论】:

谢谢,这似乎有效。我缺少的部分是如何构建服务,以便我可以修改租户并将该版本的租户服务注入我的 BackgroundService。

以上是关于从 BackgroundService 创建 DbContext 租户时配置它的主要内容,如果未能解决你的问题,请参考以下文章

从asp.net core 2.1中的控制器访问BackgroundService

android localytics 4.5.1 崩溃 NoClassDefFoundError com.localytics.android.BackgroundService

如何在 BackgroundService 获取 ASP.NET Core 启动地址

使用 ASP.NET Core 6 Minimal API 在 Azure 中停止 Web 应用程序时未调用 BackgroundService StopAsync

如何在 BackgroundService 类中制作 ServiceBusClient 和 ServiceBusProcessor DisposeAsync?

如何在 ASP.NET Core 2.1 中的计时器上运行 BackgroundService