Sau khi nghiên cứu thêm, tôi có thể trả lời tốt hơn.
Trong khi có thể chuyển chuỗi kết nối đến ISession.OpenSession
, cách tiếp cận tốt hơn là tạo một tùy chỉnh ConnectionProvider
. Phương pháp đơn giản nhất là lấy được từ DriverConnectionProvider
và ghi đè lên ConnectionString
tài sản:
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);
}
}
Sử dụng FluentNHibernate bạn thiết lập các nhà cung cấp như sau:
var config = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.Provider<TenantConnectionProvider>()
)
các ConnectionProvider được đánh giá mỗi khi bạn mở một phiên cho phép bạn kết nối với cơ sở dữ liệu người thuê cụ thể trong ứng dụng của bạn.
Vấn đề với cách tiếp cận trên là SessionFactory được chia sẻ. Đây không thực sự là vấn đề nếu bạn chỉ sử dụng bộ đệm mức đầu tiên (vì nó được gắn với phiên) nhưng nếu bạn quyết định bật bộ nhớ cache cấp thứ hai (gắn với SessionFactory).
Do đó, cách tiếp cận được khuyến nghị là có SessionFactory cho mỗi người thuê nhà (điều này sẽ áp dụng cho các chiến lược lược đồ cho mỗi đối tượng thuê và cơ sở dữ liệu cho từng người thuê).
Một vấn đề khác thường bị bỏ qua là mặc dù bộ nhớ cache cấp thứ hai được gắn với SessionFactory, trong một số trường hợp, không gian bộ nhớ cache được chia sẻ (reference). Điều này có thể được giải quyết bằng cách thiết lập thuộc tính "regionName" của nhà cung cấp.
Dưới đây là triển khai hoạt động của SessionFactory cho mỗi người thuê nhà dựa trên yêu cầu của bạn.
Lớp Tenant
chứa các thông tin chúng ta cần phải thiết lập NHibernate cho người thuê:
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();
}
}
Kể từ khi chúng tôi sẽ lưu trữ một Dictionary<Tenant, ISessionFactory>
chúng tôi thực hiện các giao diện IEquatable
vì vậy chúng tôi có thể đánh giá các phím thuê.
Quá trình nhận được người thuê nhà hiện tại là trừu tượng như vậy:
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
Cuối cùng NHibernateSessionSource
trong đó quản lý phiên:
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();
}
}
Khi chúng ta tạo một thể hiện của NHibernateSessionSource
chúng tôi thiết lập mặc định SessionFactory đến cơ sở dữ liệu "mặc định" của chúng tôi.
Khi CreateSession()
được gọi là chúng tôi nhận được ví dụ ISessionFactory
. Đây sẽ là nhà máy phiên mặc định (nếu đối tượng thuê hiện tại là null) hoặc nhà máy phiên cụ thể của người thuê nhà. Nhiệm vụ định vị nhà máy phiên cụ thể của người thuê được thực hiện theo phương pháp GetSessionFactory
.
Cuối cùng, chúng tôi gọi OpenSession
trên ví dụ ISessionFactory
mà chúng tôi đã nhận được.
Lưu ý rằng khi tạo nhà máy phiên, chúng tôi đặt tên SessionFactory (cho mục đích gỡ lỗi/lược tả) và tiền tố vùng bộ đệm (vì những lý do được đề cập ở trên).
cụ IoC của chúng tôi (trong trường hợp của tôi StructureMap) dây tất cả mọi thứ lên:
x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
x.For<ISession>().HttpContextScoped().Use(ctx =>
ctx.GetInstance<ISessionSource>().CreateSession());
x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();
Đây NHibernateSessionSource được scoped như một singleton và ISession mỗi yêu cầu.
Hy vọng điều này sẽ hữu ích.
bạn có thể làm cách nào, khi người dùng đăng nhập, để cho phép họ thay đổi mật khẩu của họ? Tôi cho rằng mật khẩu của họ nằm trong "MainDB" chứ không phải là TenantDB, đúng không? –