Nic*_*ick 7 nhibernate asp.net-mvc ninject multi-tenant
我正在构建一个多租户Web应用程序,出于安全考虑,我们需要为每个租户提供一个数据库实例.所以我有一个用于身份验证的MainDB和用于应用程序数据的许多ClientDB.
我在Ninject和Fluent nHibernate上使用Asp.net MVC.我已经在应用程序开始时在Ninject模块中使用Ninject和Fluent nHibernate设置了我的SessionFactory/Session/Repositories.我的会话是PerRequestScope,存储库也是如此.
我的问题是现在我需要为每个租户实例化一个SessionFactory(SingletonScope)实例,只要其中一个连接到应用程序并为每个webrequest创建一个新会话和必要的存储库.我很困惑如何做到这一点,需要一个具体的例子.
这是情况.
应用程序启动:TenantX的用户输入他的登录信息.创建MainDB的SessionFactory并打开与MainDB的会话以对用户进行身份验证.然后应用程序创建auth cookie.
租户访问应用程序:租户名称+ ConnectionString从MainDB中提取,Ninject必须为该租户构建租户特定的SessionFactory(SingletonScope).Web请求的其余部分,所有需要存储库的控制器将根据该租户的SessionFactory注入租户特定的会话/存储库.
如何使用Ninject设置动态?当我有多个数据库时,我最初使用的是Named实例,但现在数据库是特定于租户的,我很遗憾......
Ben*_*ter 11
经过进一步的研究,我可以给你一个更好的答案.
虽然可以将连接字符串传递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);
}
}
Run Code Online (Sandbox Code Playgroud)
使用FluentNHibernate设置提供程序,如下所示:
var config = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.Provider<TenantConnectionProvider>()
)
Run Code Online (Sandbox Code Playgroud)
每次打开会话时都会评估ConnectionProvider,从而允许您连接到应用程序中的特定于租户的数据库.
上述方法的一个问题是SessionFactory是共享的.如果您只使用第一级缓存(因为它与会话绑定),这不是一个真正的问题,但是如果您决定启用二级缓存(与SessionFactory绑定).
因此,建议的方法是每个租户使用一个SessionFactory(这适用于每个租户的架构和每个租户的数据库策略).
经常被忽略的另一个问题是尽管二级缓存与SessionFactory相关联,但在某些情况下,缓存空间本身是共享的(引用).这可以通过设置提供程序的"regionName"属性来解决.
以下是基于您的要求的每个租户SessionFactory的工作实现.
本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();
}
}
Run Code Online (Sandbox Code Playgroud)
由于我们将存储Dictionary<Tenant, ISessionFactory>我们实现IEquatable接口,因此我们可以评估租户密钥.
获取当前租户的过程是这样抽象的:
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
最后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();
}
}
Run Code Online (Sandbox Code Playgroud)
当我们创建一个实例时,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>();
Run Code Online (Sandbox Code Playgroud)
这里NHibernateSessionSource的范围是每个请求的单例和ISession.
希望这可以帮助.
| 归档时间: |
|
| 查看次数: |
2985 次 |
| 最近记录: |