One solution is to not use HttpContext
directly to get the tenant ID. Instead, make a service that you can control and inject that. That service can use the HttpContext
, but will instead use something else if you tell it. For example:
(Note: All of this is typed out manually and not tested, but should give you an idea of how it works)
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();
}
}
Tweak your 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);
}
}
Add the service to your DI container:
services.AddScoped<ITenantService, TenantService>();
And use it like this in your background service:
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>();
Note that if you need to switch to a different tenant, you may need to create a new DbContext
and scope.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…