从 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
和范围。
我有一个 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
和范围。