После дальнейших исследований я могу дать вам лучший ответ.
Хотя можно передать строку соединения в 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-per-tenant (это будет применяться к стратегиям схемы-на-арендатора и базы данных на арендатора).
Другая проблема, которая часто упускается из виду, заключается в том, что, хотя кэш второго уровня привязан кSessionFactory, в некоторых случаях само пространство кеша используется совместно ( ссылка ).Эту проблему можно решить, установив свойство «regionName» поставщика.
Ниже приведена рабочая реализация SessionFactory-per-tenant, основанная на ваших требованиях.
Класс 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 по умолчанию для нашей базы данных «по умолчанию».
Когда вызывается CreateSession()
, мы получаем экземпляр ISessionFactory
.Это будет либо фабрика сеансов по умолчанию (если текущий арендатор равен нулю), либо фабрика сеансов, специфичная для арендатора.Задача поиска фабрики сеансов для конкретного арендатора выполняется методом GetSessionFactory
.
Наконец, мы вызываем OpenSession
для полученного нами экземпляра 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>();
Здесь NHibernateSessionSource определяется как одиночный и ISession для запроса.
Надеюсь, это поможет.