Asp.net mvc 使用Fluent nHibernate和Ninject实现多租户。每个租户一个数据库
我正在构建一个多租户web应用程序,出于安全考虑,每个租户需要一个数据库实例。因此,我有一个用于身份验证的MainDB和许多用于应用程序数据的ClientDB 我正在使用Asp.net MVC与Ninject和Fluent nHibernate。在应用程序开始时,我已经在Ninject模块中使用Ninject和Fluent nHibernate设置了SessionFactory/Session/Repositories。我的会话是PerRequestScope,存储库也是 我的问题是现在我需要为每个租户实例化一个SessionFactory(SingletonScope)实例,只要其中一个租户连接到应用程序,并为每个webrequest创建一个新会话和必要的存储库。我对如何做到这一点感到困惑,需要一个具体的例子 情况是这样的 应用程序启动:租户用户输入其登录信息。将创建MainDB的SessionFactory,并打开到MainDB的会话以验证用户。然后应用程序创建身份验证cookie 租户访问应用程序:租户名称+连接字符串从MainDB中提取,Ninject必须为该租户构造特定于租户的会话工厂(SingletonScope)。在web请求的其余部分,所有需要存储库的控制器都将基于租户的SessionFactory注入特定于租户的会话/存储库Asp.net mvc 使用Fluent nHibernate和Ninject实现多租户。每个租户一个数据库,asp.net-mvc,nhibernate,ninject,multi-tenant,Asp.net Mvc,Nhibernate,Ninject,Multi Tenant,我正在构建一个多租户web应用程序,出于安全考虑,每个租户需要一个数据库实例。因此,我有一个用于身份验证的MainDB和许多用于应用程序数据的ClientDB 我正在使用Asp.net MVC与Ninject和Fluent nHibernate。在应用程序开始时,我已经在Ninject模块中使用Ninject和Fluent nHibernate设置了SessionFactory/Session/Repositories。我的会话是PerRequestScope,存储库也是 我的问题是现在我需要为
如何使用Ninject设置动态?当我有多个数据库时,我最初使用命名实例,但现在这些数据库是特定于租户的,我不知道了…进一步研究后,我可以给你一个更好的答案 虽然可以将连接字符串传递给
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);
}
}
因为我们将存储一个字典
,所以我们实现了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的作用域是单例和每个请求的ISession
希望这有帮助。如果所有数据库都在同一台计算机上,可能可以使用类映射的schema属性在预租户基础上设置数据库。这种技术的缺点是不使用nHibernate缓存。虽然更复杂,但我认为拥有多个会话工厂会更好。在这种情况下,请查看我发布的第二个链接。虽然这可以解决nHibernate缓存问题,但我的要求是每个租户一个数据库,因此每个租户一个数据库和一个架构不起作用。嗨,Nick,关键是,您可以在连接字符串提供程序中注册任何连接字符串(包括不同数据库的连接字符串)。但是,尝试了这个方法后,两个数据库看起来使用了相同的SessionFactory。在解析实体标识时是否考虑了连接字符串是值得的,否则会有冲突的风险。@BenFoster我想我理解这一点,但看起来DefaultTenantAccessor和后续的ItenantAccessor都使用相同的对象模型?您是如何根据主数据库和租户数据库中的内容来分离对象模型的?我在这里的假设是,至少会存在一个客户端和一个元数据库,或者我误解了这一点?一旦用户登录,您如何允许他们更改密码?我假设他们的密码在“MainDB”中,而不是租户数据库,对吗?
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
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();
}
}
x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
x.For<ISession>().HttpContextScoped().Use(ctx =>
ctx.GetInstance<ISessionSource>().CreateSession());
x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();