首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在ASP.NET 5 Web和实体框架7中建立多租户?

如何在ASP.NET 5 Web和实体框架7中建立多租户?
EN

Stack Overflow用户
提问于 2015-09-23 10:48:15
回答 1查看 2.9K关注 0票数 6

我正在使用ASP.NET 5的Web和EF7构建一个后端服务,以建立一个多租户数据库结构。所需经费如下:

  • API端点对所有租户都是相同的,
  • 每个租户都有自己的数据库,其中包含租户特定的数据,
  • 所有租户使用相同的数据库结构,因此可以在所有租户之间使用相同的DbContext类,但具有不同的连接字符串,
  • 每个租户数据库包含用于基于ASP.NET标识的身份验证的用户信息。

我目前正面临以下挑战:

  • DbContext实例并不是线程安全的,因此它们的生命周期应该很短。这意味着我不能简单地将DbContexts实例存储在某个地方,而必须在需要时动态创建和处理实例,
  • 必须能够动态地添加或移除租户,最好不重新启动服务,
  • EF7迁移需要工作。

为了使服务能够动态地添加或删除租户,我当前的实现基于一个JSON配置文件,该文件包含键值对中的所有租户连接字符串,如下所示:

代码语言:javascript
复制
{
   "Tenants": [
      { "Tenant1": "Server=.\\SQLEXPRESS;Database=Tenant1;integrated security=True;" },
      { "Tenant2": "Server=.\\SQLEXPRESS;Database=Tenant2;integrated security=True;" }
   ]
}

然后使用此配置来设置ContextFactory。该工厂使用DbContextOptions存储,以便在需要时动态创建DbContext实例,从而实现必要的短生命周期。工厂的定义如下:

代码语言:javascript
复制
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中的租户信息:

代码语言:javascript
复制
    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);
    }
}

然后,可以将工厂作为服务注入到任何需要它的控制器或服务,并正确地实例化所需的上下文。

到目前一切尚好。以下问题仍然存在:

如何集成EF7迁移?(已解决)

尝试添加迁移时,我得到以下错误:

System.InvalidOperationException:没有配置数据库提供程序。在设置服务时,通过重写OnConfiguring类中的DbContext类或AddDbContext方法来配置数据库提供程序。

由于租户的数量是动态的,所以我不能在DbContext类中直接指定连接字符串,也不能使用AddDbContext方法静态地向单个数据库注册DbContexts。

当我提供静态连接字符串时,迁移将被成功地创建,但是当我尝试使用我的动态方法时,这些迁移将不应用于正在使用的数据库,并且我无法在EF shell命令中指定连接字符串,以便手动或通过shell脚本执行迁移。基本上,我必须为每个租户重写一次配置代码,重新编译,然后使用shell命令来执行迁移,这不是一个值得的选择。

解决方案:

通过对要使用的每个上下文使用以下代码段迁移的上下文:

代码语言:javascript
复制
using (var context = tenantContextFactory.GetTenantContext<MyContext>(tenantId))
{
     context.Database.Migrate();
}

这将自动检查数据库是否符合最新的迁移,并在不符合时应用它。

如何集成ASP.NET恒等

需要对身份验证过程进行调整,以正确登录用户。

我现在就在上面,我会在这里发布我的最新进展。

如何在运行时更改租户?

这与前面的问题有关。如何确保可以通过编辑配置文件安全地添加或删除租户,而不必重新启动服务?这可能吗?

编辑:

我在EF7中找到了迁移问题的解决方案。下一个挑战是ASP.NET身份。

EN

回答 1

Stack Overflow用户

发布于 2015-09-23 21:33:33

正如许多人所建议的那样,拥有一个数据库会更清洁。见我在讨论中的评论。

数据库中有一个提供程序/源表。

然后创建必需接受SourceId的Repo。

代码语言:javascript
复制
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。

我知道这不是你想要的答案,但也许该考虑一下。

对我来说,代码相当雄心勃勃,维护起来将非常复杂。

但我真的希望你做得对!

更新

代码语言:javascript
复制
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
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32737557

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档