多租户用流利的NHibernate和Ninject。每个租户一个数据库 [英] Multitenancy with Fluent nHibernate and Ninject. One Database per Tenant

查看:245
本文介绍了多租户用流利的NHibernate和Ninject。每个租户一个数据库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要建在这里的安全问题,我们需要每个租户数据库的一个实例多租户web应用程序。所以,我对于应用程序数据的认证和许多ClientDB一个MainDB。

I'm building a multi-tenant web application where for security concerns, we need to have one instance of the database per tenant. So I have a MainDB for authentication and many ClientDB for application data.

我使用MVC Asp.net与Ninject和功能NHibernate。我一直在使用Ninject和功能NHibernate在Ninject模块在应用程序的启动已经设置我的SessionFactory /会话/存储库。我的会议是PerRequestScope,因为是仓库。

I am using Asp.net MVC with Ninject and Fluent nHibernate. I have already setup my SessionFactory/Session/Repositories using Ninject and Fluent nHibernate in a Ninject Module at the start of the application. My sessions are PerRequestScope, as are repositories.

我的问题是现在我需要实例化我的每个租户一个SessionFactory(SingletonScope)实例,只要其中一个连接到应用程序,并创建一个新的会话,并为每个WebRequest的必要库。我困惑的是如何做到这一点,就需要一个具体的例子。

My problem is now I need to instanciate a SessionFactory (SingletonScope) instance for each of my tenants whenever one of them connects to the application and create a new session and necessary repositories for each webrequest. I'm puzzled as to how to do this and would need a concrete example.

这里的情况。

应用程序启动:TenantX的用户输入自己的登录信息。 MainDB的SessionFactory的被创建并打开一个会议上向MainDB对用户进行认证。然后应用程序创建的cookie AUTH

Application starts : The user of TenantX enters his login info. SessionFactory of MainDB gets created and opens a session to the MainDB to authenticate the user. Then the application creates the auth cookie.

租户访问应用程序:承租人姓名+的ConnectionString从MainDB和Ninject提取必须构建为租户租户的SessionFactory(SingletonScope)。 web请求的其余部分,需要一个库中的所有控制器将基于该租户的SessionFactory租客特定的会话/资源库中注入。

Tenant accesses the application : The Tenant Name + ConnectionString are extracted from MainDB and Ninject must construct a tenant specific SessionFactory (SingletonScope) for that tenant. The rest of the web request, all controllers requiring a repository will be inject with a Tenant specific session/repository based on that tenant's SessionFactory.

我如何设置动态与Ninject?我本来使用命名实例,当我有多个数据库,但目前该数据库是特定于租户的,我迷路了......

How do I setup that dynamic with Ninject? I was originally using Named instance when I had multiple databases but now that the databases are tenant specific, I'm lost...

推荐答案

进一步研究后,我可以给你一个更好的答案。

After further research I can give you a better answer.

虽然有可能的连接字符串传递给 ISession.OpenSession 更好的方法是创建一个自定义的ConnectionProvider 。最简单的方法是从 DriverConnectionProvider 派生并重写的ConnectionString 属性:

Whilst it's possible to pass a connection string to ISession.OpenSession a better approach is to create a custom ConnectionProvider. The simplest approach is to derive from DriverConnectionProvider and override the ConnectionString property:

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您设置的提供商,像这样:

Using FluentNHibernate you set the provider like so:

var config = Fluently.Configure()
    .Database(
        MsSqlConfiguration.MsSql2008
            .Provider<TenantConnectionProvider>()
    )

本的ConnectionProvider,每次评估你打开会话,允许您连接到特定于租户的数据库应用程序中。

The ConnectionProvider is evaluated each time you open a session allowing you to connect to tenant specific databases in your application.

上述方法的一个问题是,SessionFactory的是共享的。如果你只使用一级缓存(因为这是联系在一起的会话),但如果你决定启用二级缓存(绑在SessionFactory的)这是不是一个真正的问题。

An issue with the above approach is that the SessionFactory is shared. This is not really a problem if you are only using the first level cache (since this is tied to the session) but is if you decide to enable the second level cache (tied to the SessionFactory).

因此​​,建议的方法是有一个SessionFactory每租户(这将适用于架构的每个租户和数据库每个租户的策略)。

The recommended approach therefore is to have a SessionFactory-per-tenant (this would apply to schema-per-tenant and database-per-tenant strategies).

常常被忽视的另一个问题是,虽然二级缓存是联系在一起的SessionFactory的,在某些情况下,高速缓存空间本身是共享(<一个href=\"http://ayende.com/blog/1708/nhibernate-caching-the-secong-level-cache-space-is-shared\">reference).这可以通过设置提供程序的regionName属性来解决。

Another issue often overlooked is that although the second level cache is tied to the SessionFactory, in some cases the cache space itself is shared (reference). This can be resolved by setting the "regionName" property of the provider.

下面是根据您的要求SessionFactory的每租户的工作落实。

Below is a working implementation of SessionFactory-per-tenant based on your requirements.

租户类包含我们需要设置为NHibernate的租户的信息:

The Tenant class contains the information we need to set up NHibernate for the tenant:

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();
    }
}

由于我们要存储词典&LT;租客,ISessionFactory&GT; 我们贯彻 IEquatable 接口,这样我们就可以评估租客钥匙。

Since we'll be storing a Dictionary<Tenant, ISessionFactory> we implement the IEquatable interface so we can evaluate the Tenant keys.

获取当前租户的过程抽象,像这样:

The process of getting the current tenant is abstracted like so:

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我们的默认的数据库。

When we create an instance of NHibernateSessionSource we set up a default SessionFactory to our "default" database.

了createSession()叫我们得到了一个 ISessionFactory 实例。这要么是默认的会话工厂(如果目前的租户为null)或特定于租户的会话工厂。定位租户特定会话工厂的任务是由了getSessionFactory 法进行。

When CreateSession() is called we get a ISessionFactory instance. This will either be the default session factory (if the current tenant is null) or a tenant specific session factory. The task of locating the tenant specific session factory is performed by the GetSessionFactory method.

最后,我们称之为的openSession 上,我们已经获得了 ISessionFactory 实例。

Finally we call OpenSession on the ISessionFactory instance we have obtained.

注意,当我们创建(/调试分析的目的),我们设置了一个SessionFactory名称的会话工厂和缓存区preFIX(上面提到的原因)。

我们的Io​​C容器工具(在我的情况StructureMap)导线一切行动:

Our IoC tool (in my case StructureMap) wires everything up:

    x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
    x.For<ISession>().HttpContextScoped().Use(ctx => 
        ctx.GetInstance<ISessionSource>().CreateSession());
    x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();

下面NHibernateSessionSource的作用域确定为每个请求一个单身和ISession的。

Here NHibernateSessionSource is scoped as a singleton and ISession per request.

希望这有助于。

这篇关于多租户用流利的NHibernate和Ninject。每个租户一个数据库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆