ASP.Net Core DI company context provider?

asp.net-core c# dbcontext dependency-injection entity-framework-core

Question

I am implementing a webapi with ASP.NET Core 3.

On the back-end we have a number of company databases with the exact same schema/tables etc.

I am trying to implement a company database context provider that will let me resolve the correct company database context at runtime.

In my startup.cs I have the below code which should give you an idea what I'm trying to do:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CompanyDbContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("CompanyDbConnection")));

    services.AddScoped<ICompanyRepository, CompanyRepository>();

    services.AddDbContext<System.Func<CompanyType, ICompanyDbContext>>(dbContextOptionsBuilder => key =>
    {
        switch (key)
        {
            case CompanyType.HKG:
                return dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("HkgCompanyDbConnection"));
            case CompanyType.SHG:
                return dbContextOptionsBuilder.UseSqlServer(Configuration.GetConnectionString("ShgCompanyDbConnection"));
            default:
                throw new KeyNotFoundException();
        }
    });

    //...

Instead of the generic CompanyContext which will only have 1 connection I want to inject something like a service provider but for the dbcontexts into the CompanyRepository constructor, which will then let me resolve different company dbcontexts in different cases in the repository functions (by passing arguments).

The idea is the last part of the code should return the correct context based on the key provided. But obviously the last part is not working. If it is not obvious the repository class will hold all the functions returning data from the database.

How would one go about this ?

1
0
10/4/2019 12:20:23 PM

Accepted Answer

I did something fairly similar (but not quite) recently and came across this excellent Answer which I think does exactly what you're after.

The basic steps:

Shared Delegate

public delegate IService ServiceResolver(CompanyType key);

Add Instances to be Resolved

//Note: Default Service Lifetime for AddDbContext is Scoped - perfect!
services.AddDbContext<HkgCompanyContext>(options...);
services.AddDbContext<ShgCompanyContext>(options...);

Resolver Service

services.AddScoped<ServiceResolver>(serviceProvider => key =>
{
   switch (key)
   {
      case CompanyType.HKG:
         return serviceProvider.GetService(HkgCompanyContext);
      case CompanyType.SHG:
         return serviceProvider.GetService(ShgCompanyContext);
      default:
         throw new KeyNotFoundException();
   }
}
0
10/4/2019 1:29:01 PM

Popular Answer

Following the comments this is how you can do it, by checking what is the company Id or name on the request, I have a doubt this is what you need in the end.

Oh, yes I forgot, this code was tested on .net core 2.2, but it should make a difference.

    services.AddScoped<ICompanyType, CompanyType>();
    services.AddEntityFrameworkSqlServer();
    services.AddDbContext<CompanyDbContext>((serviceProvider, options) =>
        {
            var companyCode = serviceProvider.GetRequiredService<ICompanyType>().Get();
            string connectionString = string.Empty;
            switch (companyCode)
            {
                case CompanyType.HKG:
                    connectionString = Configuration.GetConnectionString("HkgCompanyDbConnection");
                    break;
                case CompanyType.SHG:
                    connectionString = Configuration.GetConnectionString("ShgCompanyDbConnection");
                    break;
                default:
                    throw new InvalidOperationException();
            }
            options
                .UseSqlServer(connectionString)
                .UseInternalServiceProvider(serviceProvider);
        });

And some dummy company type implementation:

public class CompanyType: ICompanyType
{
    public const string HKG = "HKG";
    public const string SHG = "SHG";
    public string Get()
    {
        Random rand = new Random((int) DateTime.UtcNow.Ticks & 0x0000FFFF);
        var flipCoin = rand.Next(0, 1);
        return flipCoin == 0 ? HKG : SHG;
    }
}

public interface ICompanyType
{
    // dummy code, to be replaced with what is needed
    string Get();
}


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow