2012-01-26 8 views
7

私はマルチテナントWebアプリケーションを構築しています。セキュリティ上の理由から、テナントごとに1つのデータベースインスタンスを用意する必要があります。ですから、私は認証のためのMainDBとアプリケーションデータのための多くのClientDBを持っています。Fluent nHibernateおよびNinjectを使用したマルチテナント。テナントごとに1つのデータベース

NinjectとFluentのnHibernateでAsp.net MVCを使用しています。アプリケーションの開始時にNinjectとFluentのnHibernateをNinjectモジュールで使用してSessionFactory/Session/Repositoriesを設定しました。私のセッションはリポジトリと同様、PerRequestScopeです。

テナントごとにSessionFactory(SingletonScope)インスタンスをインスタンス化する必要があります。そのうちの1つがアプリケーションに接続し、新しいセッションと必要なリポジトリをWebリクエストごとに作成するときはいつでも問題になります。私はこれを行う方法について困惑し、具体的な例が必要です。

ここに状況があります。

アプリケーション開始:TenantXのユーザーがログイン情報を入力します。 MainDBのSessionFactoryが作成され、MainDBへのセッションが開き、ユーザーを認証します。次に、アプリケーションはauth cookieを作成します。

テナントがアプリケーションにアクセスします:テナント名+ ConnectionStringがMainDBから抽出され、Ninjectはテナント固有のSessionFactory(SingletonScope)を作成する必要があります。 Webリクエストの残りの部分は、リポジトリを必要とするすべてのコントローラに、そのテナントのSessionFactoryに基づいてテナント固有のセッション/リポジトリが挿入されます。

Ninjectでそのダイナミックを設定するにはどうすればよいですか?もともとNamedインスタンスを使用していましたが、データベースがテナント固有であるため、私は失われてしまいました...

+0

一度ユーザーがログインすれば、パスワードを変更できるようになりますか?私は彼らのパスワードがTenantDBではなく、 "MainDB"にあると仮定していますか? –

答えて

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

あなたがそうのようなプロバイダを設定FluentNHibernateを使用する:

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

されたConnectionProviderは、あなたができるようにセッションを開くたびに評価されますアプリケーション内のテナント固有のデータベースに接続します。

上記のアプローチの問題は、SessionFactoryが共有されていることです。第1レベルのキャッシュを使用している(これはセッションに結び付けられているため)ので、実際には問題にはなりませんが、第2レベルのキャッシュ(SessionFactoryに関連付けられている)を有効にすることにした場合は問題になりません。

したがって、テナントごとにSessionFactoryを設定することをお勧めします(これは、テナントごとのスキーマおよびテナントごとのデータベース戦略に適用されます)。

2番目のレベルのキャッシュはSessionFactoryに結びついていますが、キャッシュスペース自体が共有されている(reference)場合があります。これは、プロバイダの「regionName」プロパティを設定することで解決できます。

以下は、お客様の要件に基づいたテナントのSessionFactoryの実装です。我々はテナントキーを評価することができますので、我々はIEquatableインターフェイスを実装Dictionary<Tenant, ISessionFactory>を格納することがありますので

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

Tenantクラスは、私たちがテナントのためのNHibernateを設定するために必要な情報が含まれています。

現在のテナントを取得するプロセスはそれほどのように抽象化される:

セッションを管理
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を "デフォルト"のデータベースに追加します。

CreateSession()が呼び出されると、ISessionFactoryというインスタンスが生成されます。これはデフォルトのセッションファクトリ(現在のテナントがnullの場合)またはテナント固有のセッションファクトリのいずれかになります。テナント固有のセッションファクトリを見つける作業は、GetSessionFactoryメソッドによって実行されます。

最後に、取得したISessionFactoryインスタンスでOpenSessionと呼び出します。

セッションファクトリを作成するとき、セッションファクトリ名(デバッグ/プロファイリング目的)とキャッシュ領域接頭辞(上記の理由から)を設定することに注意してください。

当社のIoCツール(私の場合のStructureMap)のワイヤのすべてをバックアップ:ここ

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

をNHibernateSessionSourceは、要求ごとにシングルトンとISessionとしてスコープされています。

これが役に立ちます。

+0

この手法には、nHibernateキャッシュを使用しないという欠点があります。より複雑ではありますが、複数のセッションファクトリを持つ方が望ましいと思います。 – Nick

+0

その場合、私が投稿した第2のリンクをチェックしてください。 –

+0

これはnHibernateキャッシュの問題に対処できますが、テナントごとに1つのデータベースが必要なため、テナントごとに1つのスキーマを持つ1つのデータベースを使用することはできません。 – Nick

0

すべてのデータベースが同じマシン上にある場合は、クラスマッピングのスキーマプロパティを使用して、テナント前の基準でデータベースを設定できます。

関連する問題