Home > Back-end >  Register and inject multiple AppDbContext (with same fields) for each company in .NET Core
Register and inject multiple AppDbContext (with same fields) for each company in .NET Core

Time:12-05

I have a baseDb database and BaseDbContext like:

public class BaseDbContext : DbContext, IBaseDbContext {
    public DbSet<BaseCompany> BaseCompanies { get; set; }
    public DbSet<BaseUser> BaseUsers { get; set; }}

It only has the companies and their own database connectionStrings, so when any user requests something I get it from his database based on his companyId.

I added the baseDbContext at Startup.cs like this:

services.AddDbContext<BaseDbContext>((serviceProvider, optionsBuilder) => {
                optionsBuilder.UseNpgsql(connectionString,
                    b => b.MigrationsAssembly(typeof(BaseDbContext).Assembly.FullName));
                optionsBuilder.UseApplicationServiceProvider(serviceProvider);
            });

Now when the server starts I need to loop through the companies records from baseDb and open a connection then inject it (services.AddDbContext), how can I do that??

The thing I've done lately (it is not accurate), I created a Dictionary that hold the connectionString and the ApplicationDbContexts and I set them after the connection with baseDb is opened like:

    public static Dictionary<string, ApplicationDbContext> CompaniesDbContext { get; set; } //* connectionString : DbContext

    private static async Task<ApplicationDbContext> ConnectToDb(string ConnectionString) {
        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        optionsBuilder.UseNpgsql(ConnectionString,
                b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
        var context = new ApplicationDbContext(optionsBuilder.Options, _currentUserService, _dateTime);
        context.Database.Migrate();
        await new ApplicationDbContextSeed(context).SeedRegistryAsync();
        return context;
    }

I use this static variable at every request to get the company DbContext to use it.

The question is: How to register multi dbContext (in my case is ApplicationDbContext) so when the user requests something I get it from its company database.

The static way gives an error when 2 requests execute at the same time because the ApplicationDbContext is not injected at the dependency injection in Startup.cs.

ystem.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

CodePudding user response:

To do so:

  • save the company key in the user token.
  • in your startup.cs add you dbContext
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>( );
services.AddDbContext<ApplicationDbContext>( );
  • in your ApplicationDbContext.cs

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, HttpContextAccessor httpContextAccessor)
            : base(options) {
            _httpContext = httpContextAccessor?.HttpContext;
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
    if (optionsBuilder.IsConfigured) {
        return;
    }

    var key = _httpContext?.User.Claims
       .Where(c => c.Type == "company")
       .Select(c => c.Value)
       .SingleOrDefault();

    var conString = new PrivateStorageService().GetConnectionString(key);
    optionsBuilder.UseNpgsql(conString);
}

and then call your ApplicationDbContext from any class from the constructor. and you should get the context for the logged-in user

CodePudding user response:

Instead of saving a static list of ApplicationDbContext, I saved only the DbContextOptionsBuilder and when getting the context I created a new Instance of ApplicationDbContext class passing the corresponding optionBuilder and it's working.

  • Related