Home > Net >  How to register and get multiple DbContexts of the same type?
How to register and get multiple DbContexts of the same type?

Time:11-29

So imagine you want to access the multiple dbContexts that are on different environments or servers (Dev, Test, Pre-prod etc), and use the data from all those different databases to calculate something. How would I register multiple dbContexts of the same type (MonitoringDbContext) and differentiate between them?

This is where I got stuck

var envDbContextDetails = configuration.GetSection("EnvironmentConnectionStrings").Get<EnvironmentConnectionStringsModel>();

var nonRegisteredDbContexts = envDbContextDetails.DbConnectionStrings.Where(x => x.Environment != envDbContextDetails.CurrentDbEnvironment).ToList();            

nonRegisteredDbContexts.ForEach(x => services.AddDbContext<MonitoringDbContext>(options => options.UseSqlServer(x.ConnectionString)));

So I'm registering multiple MonitoringDbContext contexts, and now what? My idea was to resolve, pull them and add them in a Dictionary<string, MonitoringDbContext> where the key is the environment name (Dev, Test etc) and use them from a DbContextFactory where I would select the one I need, or use all of them in a loop, depending on what I need to calculate. But I have no idea how to get the dbContexts after registering them, and how to differentiate between them.

CodePudding user response:

Usually applications should be environment agnostic, being different environments just different instances of the same application, but that's clearly not your case.

You could have child classes of your MonitoringDbContext, that passes through the constructor which connection string is to be used. That way you can have one instance for each environment in a more declarative way.

If you don't have a fixed number of environments that can be known at compile time, you would have to implement some kind of Factory yourself. Such structure would hold or return an instance for every environment that you have. Note that you would have to inject that Factory into your classes, rather than injecting the MonitoringDbContext directly.

CodePudding user response:

You can register multiple instances of a service in the services collection, and get all of them by resolving IEnumerable<T> where T is the type of your service.

Knowing that, you should inspect the code of your library and check if registration of services do simple Add in the IServiceCollection. EF Core use TryAddX, so just your first registration is included in the service collection. So we have to customize the registration.

You can create a wrapper like that:

public class EnvWrapper<T>
{
    public string Env { get; }
    public T Value { get; }

    public EnvWrapper(string env, T value)
    {
        Env = env;
        Value = value;
    }
}

Then register the basics services EF Core needs to run:

services.AddEntityFrameworkSqlServer();

Then foreach environment, register an EnvWrapper with an instance of your DbContext:

var connectionStringsByEnv = new Dictionary<string, string>
{
    ["dev"] = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=tests;Integrated Security=True;",
    ["prd"] = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=tests;Integrated Security=True;"
};

var services = new ServiceCollection();
services.AddEntityFrameworkSqlServer();

foreach (var cs in connectionStringsByEnv)
{
    var dbContextOptions = new DbContextOptionsBuilder<MonitoringDbContext>()
            .UseSqlServer(cs.Value)
            .Options;

    services.AddScoped(_ =>
    {
        return new EnvWrapper<MonitoringDbContext>(cs.Key, new MonitoringDbContext(dbContextOptions));
    });
}

If you want, you can resolve IEnumerable<Wrapper<MonitoringDbContext>> and iterate on the result:

public class MonitoringDbContextStore : IReadOnlyDictionary<string, MonitoringDbContext>
{
    private readonly Dictionary<string, MonitoringDbContext> _contexts;

    public MonitoringDbContextStore(IEnumerable<EnvWrapper<MonitoringDbContext>> contexts)
    {
        _contexts = contexts.ToDictionary(c => c.Env, c => c.Value/*, StringComparer.OrdinalIgnoreCase*/);
    }
}

Maybe you should register MonitoringDbContextStore in your service collection.

If you want/need IDbContextFactory, you can add more services:

services.AddSingleton<IDbContextFactorySource<MonitoringDbContext>, DbContextFactorySource<MonitoringDbContext>>();

And per environment:

services.AddScoped(sp =>
{
    var factorySource = sp.GetRequiredService<IDbContextFactorySource<MonitoringDbContext>>();
    return new EnvWrapper<IDbContextFactory<MonitoringDbContext>>(cs.Key, new DbContextFactory<MonitoringDbContext>(sp, dbContextOptions, factorySource));
});

Again, you can resolve a collection of EnvWrapper with the type IEnumerable<EnvWrapper<IDbContextFactory<MonitoringDbContext>>>.

  • Related