How to configure multiple leases in ASP.NET 5 Web API and Entity Framework 7?

I am creating a backend service using ASP.NET5 Web API and EF7 to set up a multi-tenant database structure. The requirements are as follows:

  • API endpoints are the same for all tenants,
  • each tenant has its own database containing specific data of the tenant,
  • all tenants use the same database structure, therefore the same DbContext classes can be used for all tenants, but with different connection strings,
  • each tenant database contains user information for authentication based on an ASP.NET identifier.

At the moment, I have encountered the following problems:

  • DbContext instances are not thread safe, so their life cycle should be short. This means that I cannot just store DbContexts instances somewhere, but dynamically create and delete instances if necessary,
  • It should be possible to add or remove tenants dynamically, preferably without restarting the service,
  • You must complete the migration of EF7.

To enable the service for dynamically adding or removing tenants, my current implementation is based on the JSON configuration file, which contains all tenant connection strings in key-value pairs, for example:

{
   "Tenants": [
      { "Tenant1": "Server=.\\SQLEXPRESS;Database=Tenant1;integrated security=True;" },
      { "Tenant2": "Server=.\\SQLEXPRESS;Database=Tenant2;integrated security=True;" }
   ]
}

ContextFactory. factory DbContextOptions DbContext , , . factory :

public class TenantContextFactory : ITenantContextFactory
{
    /// <summary>
    /// The tenant configurations store.
    /// </summary>
    private IDictionary<string, DbContextOptions> tenants;

    /// <summary>
    /// Creates a new TenantContextFactory
    /// </summary>
    public TenantContextFactory()
    {
        tenants = new Dictionary<string, DbContextOptions>();
    }

    /// <summary>
    /// Registers a tenant configuration with the store.
    /// </summary>
    /// <param name="id">The tenant id.</param>
    /// <param name="options">The context options.</param>
    public void RegisterTenant(string id, DbContextOptions options)
    {
        if (!tenants.ContainsKey(id))
        {
            tenants.Add(id, options);
        }
    }

    /// <summary>
    /// Creates a DbContext instance for the specified tenant.
    /// </summary>
    /// <typeparam name="T">The type of DbContext to create.</typeparam>
    /// <param name="id">The tenant id.</param>
    /// <returns>A new instance of the desired DbContext</returns>
    public T GetTenantContext<T>(string id) where T : DbContext
    {
        DbContextOptions options;
        if (tenants.TryGetValue(id, out options))
        {
            // get the type of the desired DbContext and return a new instance
            // with the DbContextOptions as the constructor parameter
            return (T)Activator.CreateInstance(typeof(T), options);
        }

        return null;
    }
}

ContextFactory , :

    public static class ExtensionMethods
{
    /// <summary>
    /// Adds multi tenancy to the service.
    /// </summary>
    /// <param name="services">The service collection</param>
    /// <param name="config">The configuration object</param>
    public static void AddMultiTenancy(this IServiceCollection services, IConfiguration config)
    {
        var tenantContextFactory = new TenantContextFactory();

        // get the information from the JSON file
        var tenants = config.GetSection("Tenants");
        var values = tenants.GetChildren();
        foreach (var key in values)
        {
            foreach (var item in key.GetChildren())
            {
                // get the correct name of the config node
                var tenantId = item.Key.Split(':').Last();

                // and the connection string
                var connectionString = item.Value;

                // create the OptionsBuilder and configure it to use SQL server with the connection string
                var builder = new DbContextOptionsBuilder();
                builder.UseSqlServer(connectionString);

                // and register it with the factory
                tenantContextFactory.RegisterTenant(tenantId, builder.Options);
            }   
        }

        // register the factory with the DI container
        services.AddInstance(typeof(ITenantContextFactory), tenantContextFactory);
    }
}

factory , , .

. :

EF7? ()

, :

System.InvalidOperationException: . OnConfiguring DbContext AddDbContext .

, DbContext AddDbContext DbContexts .

, , , , EF shell script. , , , .

:

, , , :

using (var context = tenantContextFactory.GetTenantContext<MyContext>(tenantId))
{
     context.Database.Migrate();
}

, , , .

ASP.NET?

.

.

?

. , , ? ?

EDIT:

EF7. - ASP.NET.

+4
1

, . .

/.

Repo, SourceId.

public interface IRepository<T>    
{        
    T GetById(int sourceid, int id);
}

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    public TEntity GetById(int sourceId, int id)
    {
        return _dbContext.Set<TEntity>().Find(sourceId, id);
    }
}

, SourceId .

, , , , , - .

.

, !

using(var context = new MyContext(DbHelper.GetConnectionString()))
{

}

public static class DbHelper
{
    public static string GetConnectionString()
    {
        //some logic to get the corrosponding connection string
        // which you are wanting  this could be based of url
    }
}
-1

Source: https://habr.com/ru/post/1608653/


All Articles