1

in my team we have multiple databases, one for every client that works with us. In our API we only work with one database, so we do dependency injection of the DbContext using the connection string loaded inside appsettings (one instance of this API for every client). Now we have to access different databases with the same structure, and we don't know how to work properly with dependency injection in this case.

The way we have to obtain the connection string for every client is querying a parametry database that has them in a table.

enter image description here

We receive a request that contains that client_id and, based on that id, we obtain the connection string and use it to access the database.

So we must find a way to do DI after we have obtained that connection string that I need in that moment.

3
  • Maybe this can help: stackoverflow.com/questions/41525856/… Commented Aug 16, 2022 at 19:51
  • And also check this. Commented Aug 16, 2022 at 20:02
  • Dependency injection is probably the wrong way to achieve this. You need a parametrised constructor for your API which accepts the connection string. Providing your DI framework is smart enough to cope with more than just simple New() methods then you should be able to continue as before (if not then take a look at Autofac) Commented Aug 16, 2022 at 21:44

1 Answer 1

1

Instead of passing connection string like this you can adapt the following multi tenancy pattern.

Define Tenant

public class Tenant
{
    public string Name { get; set; }
    public string TID { get; set; }
    public string ConnectionString { get; set; }
}

Tenant Settings

public class TenantSettings
{
    public Configuration Defaults { get; set; }
    public List<Tenant> Tenants { get; set; }
}

And Configuration

 public class Configuration
{
    public string DBProvider { get; set; }
    public string ConnectionString { get; set; }
}

Your connection string structure will be following like this

"TenantSettings": {
  "Defaults": {
    "DBProvider": "mssql",
    "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=sharedTenantDb;Integrated Security=True;MultipleActiveResultSets=True"
  },
  "Tenants": [
    {
      "Name": "alpha",
      "TID": "alpha",
      "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=alphaTenantDb;Integrated Security=True;MultipleActiveResultSets=True"
    },
    {
      "Name": "beta",
      "TID": "beta",
      "ConnectionString": "Data Source=(localdb)\\mssqllocaldb;Initial Catalog=betaTenantDb;Integrated Security=True;MultipleActiveResultSets=True"
    }
  ]
}

Our tenant service will be like this

public interface ITenantService
{
    public string GetDatabaseProvider();
    public string GetConnectionString();
    public Tenant GetTenant();
}

Implementation

public class TenantService : ITenantService
{
    private readonly TenantSettings _tenantSettings;
    private HttpContext _httpContext;
    private Tenant _currentTenant;
    public TenantService(IOptions<TenantSettings> tenantSettings, IHttpContextAccessor contextAccessor)
    {
        _tenantSettings = tenantSettings.Value;
        _httpContext = contextAccessor.HttpContext;
        if (_httpContext != null)
        {
            if (_httpContext.Request.Headers.TryGetValue("tenant", out var tenantId))
            {
                SetTenant(tenantId);
            }
            else
            {
                throw new Exception("Invalid Tenant!");
            }
        }
    }
    private void SetTenant(string tenantId)
    {
        _currentTenant = _tenantSettings.Tenants.Where(a => a.TID == tenantId).FirstOrDefault();
        if (_currentTenant == null) throw new Exception("Invalid Tenant!");
        if (string.IsNullOrEmpty(_currentTenant.ConnectionString))
        {
            SetDefaultConnectionStringToCurrentTenant();
        }
    }
    private void SetDefaultConnectionStringToCurrentTenant()
    {
        _currentTenant.ConnectionString = _tenantSettings.Defaults.ConnectionString;
    }
    public string GetConnectionString()
    {
        return _currentTenant?.ConnectionString;
    }
    public string GetDatabaseProvider()
    {
        return _tenantSettings.Defaults?.DBProvider;
    }
    public Tenant GetTenant()
    {
        return _currentTenant;
    }
}

Extend the DBContext

public class ApplicationDbContext : DbContext
{
    public string TenantId { get; set; }

    private readonly ITenantService _tenantService;

    public ApplicationDbContext(DbContextOptions options, ITenantService tenantService) : base(options)
    {
        _tenantService = tenantService;
        TenantId = _tenantService.GetTenant()?.TID;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var tenantConnectionString = _tenantService.GetConnectionString();
        if (!string.IsNullOrEmpty(tenantConnectionString))
        {
            var DBProvider = _tenantService.GetDatabaseProvider();
            if (DBProvider.ToLower() == "mssql")
            {
                optionsBuilder.UseSqlServer(_tenantService.GetConnectionString());
            }
        }
    }
}

There you have it, You can access your current tenant through DI (ITenantService) and have your current DBContext configured also you can do several operation depends on the TenantId .

Sign up to request clarification or add additional context in comments.

1 Comment

Is there a way to do this with the infrastructure we currently have? I mean the parametry DB

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.