Asp.net mvc 使用Fluent nHibernate和Ninject实现多租户。每个租户一个数据库

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,存储库也是 我的问题是现在我需要为

我正在构建一个多租户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注入特定于租户的会话/存储库


如何使用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>();