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.csadd 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.
