我最近需要解决这个问题,以开发一个可靠的多租户连接,该连接不仅能够支持位于不同服务器上的租户数据库(可以使用连接字符串解决),而且还支持每个模式的租户。这里的棘手之处在于您的实体映射需要反映架构名称以解析表。这假设所有租户都使用相同的数据库架构,只是架构名称不同。
在我的情况下,我使用的是 Mehdi.me DbContextScope,它部分地支持了我需要的东西,因为您可以覆盖并提供 IDbContextFactory 实现以使用适当的连接字符串创建您的 DbContexts。第二点我对工厂进行了一些扩展,以确保租户架构细节可以通过实体配置的初始化。
随意看看它是否适合您的项目,或者给您一些想法如何获得您需要的东西。 (https://github.com/StevePy/DbContextScope)
实现需要一些设置,显然已经习惯了 DbContextFactory/DbContextLocator 模式的工作单元,但您大致需要以下设置:
创建一个类来表示实现IDbTenant 的租户连接详细信息。此类将链接到当前租户实例,返回租户表所在位置的连接字符串和架构名称。
在您的项目中实现IDbContextFactory,它将构造DbContext 实例。这个工厂通常接受一个默认构造函数,一个连接字符串,现在创建IDbTenant 的实例是第 1 步。
初始化您的 IoC(如果存在)以使用在第 2 步中创建的 IDbContextFactory 初始化 DbContextScopeFactory。这看起来像:
ioc.Register<IDbContextScopeFactory>( ()=> {new DbContextScopeFactory(new SqlServerTenantDbContextFactory());});
其中SqlServerTenantDbContextFactory是第2步创建的实现。以上大致是Autofac IoC的注册过程。本质上,您只是想确保当 DbContextScopeFactory 被实例化时,您将其提供给您的 DbContextFactory。
ContextFactory 的实现。
public class SqlServerTenantDbContextFactory : IDbContextFactory
{
TDbContext IDbContextFactory.CreateDbContext<TDbContext>()
{
return (TDbContext)Activator.CreateInstance<TDbContext>();
}
TDbContext IDbContextFactory.CreateDbContext<TDbContext>(IDbTenant tenant)
{
var connection = DbProviderFactories.GetFactory("System.Data.SqlClient").CreateConnection();
// based on the provider set up in <providers> configration under <entityFramework>...
connnection.ConnectionString = tenant?.ConnectionString;
return (TDbContext)Activator.CreateInstance(typeof(TDbContext), tenant, connection, true);
}
TDbContext IDbContextFactory.CreateDbContext<TDbContext>(string connectionString)
{
var connection = DbProviderFactories.GetFactory("System.Data.SqlClient").CreateConnection();
connnection.ConnectionString = connectionString;
return (TDbContext)Activator.CreateInstance(typeof(TDbContext), connection, true);
}
}
最后一个配置更改是EntityTypeConfiguration 添加一个接受IDbTenant 的构造函数,并将[ImportingConstructor] 属性添加到该构造函数。 ContextFactory 将负责其余的工作。因此,例如,给定一个名为“Order”的实体,您将定义一个实体类型配置,如:
public class OrderConfiguration : EntityTypeConfiguration<Order>
{
[ImportingConstructor]
public OrderConfiguration(IDbTenant tenant)
: base()
{
ToTable("Orders", tenant.SchemaName);
// HasKey(...);
// HasMany(...);
// etc. etc. etc.
}
}
不确定这是否可以适用于代码优先的实现,但我对此表示怀疑,因为该模式想要负责 db 模式定义和迁移,这在模式之间切换时会变得一团糟或服务器。
可能有很多内容需要吸收,尤其是如果您以前没有使用 Mehdi.me DbContextScope 的经验,但希望它能给您一些想法。
一旦设置了这些,与普通 Mehdi.me 模式的唯一变化是,当您使用 DbContextLocator 检索上下文时,将 IDbTenant 实例传递给它:
private MyAppContext Context
{
get { return ContextLocator.Get<MyAppContext>(_tenant); }
}
_tenant 是根据您登录的租户初始化的。 (即租户识别策略从 OWIN 身份验证或会话状态中提取详细信息......)从这里上下文工厂接管并使用租户详细信息初始化您的上下文。