Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
347 views
in Technique[技术] by (71.8m points)

c# - Configure DbContext tenant when creating it from BackgroundService

I have an ASP.NET Core 3.1 application that uses an existing Postgres database where each tenant is stored in a separate schema. This works well in HTTP requests, the tenant identifier is stored on the request and in my DbContext I have the following code in the OnConfiguring method:

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");
}

This adjusts the Postgres search path to the tenant for the current request.

I'm now trying to add a background service that also needs to use the database. My background service uses an IServiceProvider and I tried to create my DbContext as following, which obviously doesn't work due to the multitenant implementation I used:

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

The background service doesn't have a tenant like an HTTP request has, it needs to handle that aspect itself and generate DbContexts with different tenants.

I'm not sure how to adapt my context in a way so that I can create instances with different tenants. Or whether that is actually what I should be doing at all, or if there are better ways to handle this. I know the multitenancy via Postgres schema isn't a typical case for ASP.NET Core, but that's nothing I can change right now.

How can and should I use DbContext with tenants based on Postgres schemas when I need to use it from both HTTP requests and in Background services that are not associated with a specific tenant (but e.g. cycle through tenants to perform a job on each tenant)?

question from:https://stackoverflow.com/questions/65916397/configure-dbcontext-tenant-when-creating-it-from-backgroundservice

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...