经过进一步研究,我可以给你一个更好的答案。
虽然可以将连接字符串传递给ISession.OpenSession
更好的方法是创建自定义ConnectionProvider
。最简单的方法是从DriverConnectionProvider
并覆盖ConnectionString
财产:
public class TenantConnectionProvider : DriverConnectionProvider
{
protected override string ConnectionString
{
get
{
// load the tenant connection string
return "";
}
}
public override void Configure(IDictionary<string, string> settings)
{
ConfigureDriver(settings);
}
}
使用 FluentNHibernate 您可以像这样设置提供程序:
var config = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.Provider<TenantConnectionProvider>()
)
每次打开会话时都会评估 ConnectionProvider,以便您连接到应用程序中的租户特定数据库。
上述方法的一个问题是 SessionFactory 是共享的。如果您仅使用一级缓存(因为它与会话相关),那么这并不是真正的问题,但如果您决定启用二级缓存(与 SessionFactory 相关),那么这实际上并不是问题。
因此,推荐的方法是为每个租户设置一个 SessionFactory(这适用于每个租户模式和每个租户数据库策略)。
另一个经常被忽视的问题是,虽然二级缓存与 SessionFactory 绑定在一起,但在某些情况下缓存空间本身是共享的(参考)。这可以通过设置提供者的“regionName”属性来解决。
下面是根据您的要求的 SessionFactory-per-tenant 的工作实现。
The Tenant
类包含我们为租户设置 NHibernate 所需的信息:
public class Tenant : IEquatable<Tenant>
{
public string Name { get; set; }
public string ConnectionString { get; set; }
public bool Equals(Tenant other)
{
if (other == null)
return false;
return other.Name.Equals(Name) && other.ConnectionString.Equals(ConnectionString);
}
public override bool Equals(object obj)
{
return Equals(obj as Tenant);
}
public override int GetHashCode()
{
return string.Concat(Name, ConnectionString).GetHashCode();
}
}
因为我们将存储一个Dictionary<Tenant, ISessionFactory>
我们实施IEquatable
接口,以便我们可以评估租户密钥。
获取当前租户的过程抽象如下:
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
最后NHibernateSessionSource
它管理会话:
public interface ISessionSource
{
ISession CreateSession();
}
public class NHibernateSessionSource : ISessionSource
{
private Dictionary<Tenant, ISessionFactory> sessionFactories =
new Dictionary<Tenant, ISessionFactory>();
private static readonly object factorySyncRoot = new object();
private string defaultConnectionString =
@"Server=(local)\sqlexpress;Database=NHibernateMultiTenancy;integrated security=true;";
private readonly ISessionFactory defaultSessionFactory;
private readonly ITenantAccessor tenantAccessor;
public NHibernateSessionSource(ITenantAccessor tenantAccessor)
{
if (tenantAccessor == null)
throw new ArgumentNullException("tenantAccessor");
this.tenantAccessor = tenantAccessor;
lock (factorySyncRoot)
{
if (defaultSessionFactory != null) return;
var configuration = AssembleConfiguration("default", defaultConnectionString);
defaultSessionFactory = configuration.BuildSessionFactory();
}
}
private Configuration AssembleConfiguration(string name, string connectionString)
{
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
)
.Mappings(cfg =>
{
cfg.FluentMappings.AddFromAssemblyOf<NHibernateSessionSource>();
})
.Cache(c =>
c.UseSecondLevelCache()
.ProviderClass<HashtableCacheProvider>()
.RegionPrefix(name)
)
.ExposeConfiguration(
c => c.SetProperty(NHibernate.Cfg.Environment.SessionFactoryName, name)
)
.BuildConfiguration();
}
private ISessionFactory GetSessionFactory(Tenant currentTenant)
{
ISessionFactory tenantSessionFactory;
sessionFactories.TryGetValue(currentTenant, out tenantSessionFactory);
if (tenantSessionFactory == null)
{
var configuration = AssembleConfiguration(currentTenant.Name, currentTenant.ConnectionString);
tenantSessionFactory = configuration.BuildSessionFactory();
lock (factorySyncRoot)
{
sessionFactories.Add(currentTenant, tenantSessionFactory);
}
}
return tenantSessionFactory;
}
public ISession CreateSession()
{
var tenant = tenantAccessor.GetCurrentTenant();
if (tenant == null)
{
return defaultSessionFactory.OpenSession();
}
return GetSessionFactory(tenant).OpenSession();
}
}
当我们创建一个实例时NHibernateSessionSource
我们为我们的“默认”数据库设置了一个默认的 SessionFactory。
When CreateSession()
被称为我们得到一个ISessionFactory
实例。这将是默认会话工厂(如果当前租户为空)或特定于租户的会话工厂。定位租户特定会话工厂的任务由GetSessionFactory
method.
最后我们打电话OpenSession
on the ISessionFactory
我们获得的实例。
请注意,当我们创建会话工厂时,我们设置 SessionFactory 名称(用于调试/分析目的)和缓存区域前缀(出于上述原因)。
我们的 IoC 工具(在我的例子中为 StructureMap)将所有内容连接起来:
x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
x.For<ISession>().HttpContextScoped().Use(ctx =>
ctx.GetInstance<ISessionSource>().CreateSession());
x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();
这里 NHibernate Session Source 的作用域是单例和每个请求的 Session。
希望这可以帮助。