2013-10-18 8 views
25

私たちの組織では、テナントIDアーキテクチャではなく、単一のデータベース、マルチテナント
テーブルスキーマ別)が必要です。コードの最初のマルチテナントEF6

ここではこの種のものを使い始める上で、ここで素晴らしい記事があります: http://romiller.com/2011/05/23/ef-4-1-multi-tenant-with-code-first/

は、記事の途中では、これが書かれている:

あなたはと、おそらく(わかりますいくつかの不満)、各エンティティのテーブルスキーマを に設定するコードを書く必要があります。確かに、 このコードの周りには多くの魔法のユニコーンが放牧されていません... EF の将来のバージョンでは、これをよりクリーンなカスタムコンベンションに置き換えることができます。

私たちの目標は、同じモデルを持つ複数のスキーマに接続するために使用できる単一のコンテキストクラスを可能にすることです。
(それは初回のみEFは、コンテキストを初期化し、OnModelCreatingを実行することを適用するためmodelBuilder.HasDefaultSchemaは、十分な
を思われないことを注)

は、前述のクリーナーカスタム大会はEF5またはEF6に存在していますか?
これをどうにかして処理するクリーナーの方法はありますか?

注:私は開発フォーラムでこの質問をしました。これはEFの方針にもっと関連しているようですが、ここに誰かが代替案を持っているかどうかを見たいと思っていました。

注2:私は移行について心配していません。私たちはそれを別々に扱います。

+0

興味があるだけ - どのように多くのテナント?同時ユーザー数は? – tsells

+0

テナントは100年代に入る可能性があります。同時ユーザーは低く、多分100人は私の推測です。 –

+1

なぜデータベース設計レベルでそれを処理しないのですか?たとえば、テナントIDを含むすべてのテーブルの列をリポジトリレベルで処理します。 –

答えて

33

プロパティmodelBuilder.HasDefaultSchemaで十分です。モデルは一度作成され、EntityFramworkによって内部的にキャッシュされ、キャッシュ用に独自のキーを定義することができます。スキーマ名をモデルキャッシュキーとし、EFはすべての異なるキャッシュキー(ここではスキーマ)によってモデルを作成します。ここに私の概念実証コードがあります:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

using TenantDataModel; 

namespace TenantDataContext 
{ 
    public class TenantDataCtx : DbContext, IDbModelCacheKeyProvider 
    { 
     #region Construction 

     public static TenantDataCtx Create(string databaseServer, string databaseName, string databaseUserName, string databasePassword, Guid tenantId) 
     { 
      var connectionStringBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder(); 
      connectionStringBuilder.DataSource = databaseServer; 
      connectionStringBuilder.InitialCatalog = databaseName; 
      connectionStringBuilder.UserID = databaseUserName; 
      connectionStringBuilder.Password = databasePassword; 

      string connectionString = connectionStringBuilder.ToString(); 
      return new TenantDataCtx(connectionString, tenantId); 
     } 

     // Used by EF migrations 
     public TenantDataCtx() 
     { 
      Database.SetInitializer<TenantDataCtx>(null); 
     } 

     internal TenantDataCtx(string connectionString, Guid tenantId) 
      : base(connectionString) 
     { 
      Database.SetInitializer<TenantDataCtx>(null); 
      this.SchemaName = tenantId.ToString("D"); 
     } 

     public string SchemaName { get; private set; } 

     #endregion 

     #region DataSet Properties 

     public DbSet<TestEntity> TestEntities { get { return this.Set<TestEntity>(); } } 

     #endregion 

     #region Overrides 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      if (this.SchemaName != null) 
      { 
       modelBuilder.HasDefaultSchema(this.SchemaName); 
      } 

      base.OnModelCreating(modelBuilder); 
     } 

     #endregion 

     #region IDbModelCacheKeyProvider Members 

     public string CacheKey 
     { 
      get { return this.SchemaName; } 
     } 

     #endregion 
    } 
} 

さらに私はEF移行を使用する方法を見つけました。私は私のソリューションには本当に満足していませんが、現在利用可能な他のソリューションはないようです。

using System; 
using System.Collections.Generic; 
using System.Data.Entity.SqlServer; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace TenantDatabaseManager 
{ 
    public class SqlServerSchemaAwareMigrationSqlGenerator : SqlServerMigrationSqlGenerator 
    { 
     private string _schema; 

     public SqlServerSchemaAwareMigrationSqlGenerator(string schema) 
     { 
      _schema = schema; 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.AddColumnOperation addColumnOperation) 
     { 
      string newTableName = _GetNameWithReplacedSchema(addColumnOperation.Table); 
      var newAddColumnOperation = new System.Data.Entity.Migrations.Model.AddColumnOperation(newTableName, addColumnOperation.Column, addColumnOperation.AnonymousArguments); 
      base.Generate(newAddColumnOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.AddPrimaryKeyOperation addPrimaryKeyOperation) 
     { 
      addPrimaryKeyOperation.Table = _GetNameWithReplacedSchema(addPrimaryKeyOperation.Table); 
      base.Generate(addPrimaryKeyOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.AlterColumnOperation alterColumnOperation) 
     { 
      string tableName = _GetNameWithReplacedSchema(alterColumnOperation.Table); 
      var newAlterColumnOperation = new System.Data.Entity.Migrations.Model.AlterColumnOperation(tableName, alterColumnOperation.Column, alterColumnOperation.IsDestructiveChange); 
      base.Generate(newAlterColumnOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.DropPrimaryKeyOperation dropPrimaryKeyOperation) 
     { 
      dropPrimaryKeyOperation.Table = _GetNameWithReplacedSchema(dropPrimaryKeyOperation.Table); 
      base.Generate(dropPrimaryKeyOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.CreateIndexOperation createIndexOperation) 
     { 
      string name = _GetNameWithReplacedSchema(createIndexOperation.Table); 
      createIndexOperation.Table = name; 
      base.Generate(createIndexOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.CreateTableOperation createTableOperation) 
     { 
      string newTableName = _GetNameWithReplacedSchema(createTableOperation.Name); 
      var newCreateTableOperation = new System.Data.Entity.Migrations.Model.CreateTableOperation(newTableName, createTableOperation.AnonymousArguments); 
      newCreateTableOperation.PrimaryKey = createTableOperation.PrimaryKey; 
      foreach (var column in createTableOperation.Columns) 
      { 
       newCreateTableOperation.Columns.Add(column); 
      } 

      base.Generate(newCreateTableOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.RenameTableOperation renameTableOperation) 
     { 
      string oldName = _GetNameWithReplacedSchema(renameTableOperation.Name); 
      string newName = renameTableOperation.NewName.Split(new char[] { '.' }).Last(); 
      var newRenameTableOperation = new System.Data.Entity.Migrations.Model.RenameTableOperation(oldName, newName, renameTableOperation.AnonymousArguments); 
      base.Generate(newRenameTableOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.RenameIndexOperation renameIndexOperation) 
     { 
      string tableName = _GetNameWithReplacedSchema(renameIndexOperation.Table); 
      var newRenameIndexOperation = new System.Data.Entity.Migrations.Model.RenameIndexOperation(tableName, renameIndexOperation.Name, renameIndexOperation.NewName); 
      base.Generate(newRenameIndexOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.AddForeignKeyOperation addForeignKeyOperation) 
     { 
      addForeignKeyOperation.DependentTable = _GetNameWithReplacedSchema(addForeignKeyOperation.DependentTable); 
      addForeignKeyOperation.PrincipalTable = _GetNameWithReplacedSchema(addForeignKeyOperation.PrincipalTable); 
      base.Generate(addForeignKeyOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.DropColumnOperation dropColumnOperation) 
     { 
      string newTableName = _GetNameWithReplacedSchema(dropColumnOperation.Table); 
      var newDropColumnOperation = new System.Data.Entity.Migrations.Model.DropColumnOperation(newTableName, dropColumnOperation.Name, dropColumnOperation.AnonymousArguments); 
      base.Generate(newDropColumnOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.RenameColumnOperation renameColumnOperation) 
     { 
      string newTableName = _GetNameWithReplacedSchema(renameColumnOperation.Table); 
      var newRenameColumnOperation = new System.Data.Entity.Migrations.Model.RenameColumnOperation(newTableName, renameColumnOperation.Name, renameColumnOperation.NewName); 
      base.Generate(newRenameColumnOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.DropTableOperation dropTableOperation) 
     { 
      string newTableName = _GetNameWithReplacedSchema(dropTableOperation.Name); 
      var newDropTableOperation = new System.Data.Entity.Migrations.Model.DropTableOperation(newTableName, dropTableOperation.AnonymousArguments); 
      base.Generate(newDropTableOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.DropForeignKeyOperation dropForeignKeyOperation) 
     { 
      dropForeignKeyOperation.PrincipalTable = _GetNameWithReplacedSchema(dropForeignKeyOperation.PrincipalTable); 
      dropForeignKeyOperation.DependentTable = _GetNameWithReplacedSchema(dropForeignKeyOperation.DependentTable); 
      base.Generate(dropForeignKeyOperation); 
     } 

     protected override void Generate(System.Data.Entity.Migrations.Model.DropIndexOperation dropIndexOperation) 
     { 
      dropIndexOperation.Table = _GetNameWithReplacedSchema(dropIndexOperation.Table); 
      base.Generate(dropIndexOperation); 
     } 

     private string _GetNameWithReplacedSchema(string name) 
     { 
      string[] nameParts = name.Split('.'); 
      string newName; 

      switch (nameParts.Length) 
      { 
       case 1: 
        newName = string.Format("{0}.{1}", _schema, nameParts[0]); 
        break; 

       case 2: 
        newName = string.Format("{0}.{1}", _schema, nameParts[1]); 
        break; 

       case 3: 
        newName = string.Format("{0}.{1}.{2}", _schema, nameParts[1], nameParts[2]); 
        break; 

       default: 
        throw new NotSupportedException(); 
      } 

      return newName; 
     } 
    } 
} 

そして、これは私がSqlServerSchemaAwareMigrationSqlGeneratorを使用する方法である:ドイツから

// Update TenantDataCtx 
var tenantDataMigrationsConfiguration = new DbMigrationsConfiguration<TenantDataContext.TenantDataCtx>(); 
tenantDataMigrationsConfiguration.AutomaticMigrationsEnabled = false; 
tenantDataMigrationsConfiguration.SetSqlGenerator("System.Data.SqlClient", new SqlServerSchemaAwareMigrationSqlGenerator(schemaName)); 
tenantDataMigrationsConfiguration.SetHistoryContextFactory("System.Data.SqlClient", (existingConnection, defaultSchema) => new HistoryContext(existingConnection, schemaName)); 
tenantDataMigrationsConfiguration.TargetDatabase = new System.Data.Entity.Infrastructure.DbConnectionInfo(connectionString, "System.Data.SqlClient"); 
tenantDataMigrationsConfiguration.MigrationsAssembly = typeof(TenantDataContext.TenantDataCtx).Assembly; 
tenantDataMigrationsConfiguration.MigrationsNamespace = "TenantDataContext.Migrations.TenantData"; 

DbMigrator tenantDataCtxMigrator = new DbMigrator(tenantDataMigrationsConfiguration); 
tenantDataCtxMigrator.Update(); 

よろしく、

トビアス

+0

こんにちはTobias。 「これはSqlServerSchemaAwareMigrationSqlGeneratorを使用する方法です」というコードはどこで呼び出されますか?これをConfigurationに接続しますか?他にもこれを運んでいますか?オランダ出身、ピーター –

+3

こんにちは。このコードは、 'tenantDataMigrationsConfiguration.MigrationsAssembly'に存在するすべての移行を 'tenantDataMigrationsConfiguration.TargetDatabase'で識別されるデータベースに適用します。あなたはどこでもそのコードを実行することができます。私はコマンドライン引数を介してすべての必要なパラメータを取得し、それと接続文字列を構築し、コードを実行する小さなコンソールアプリケーションがあります。 –

+3

重要なアドバイスの1つ:SqlServerSchemaAwareMigrationSqlGeneratorは完全に完了していません。いくつかの日前に私は 'Generate(RenameTableOperation renameTableOperation)'のオーバーロードを上書きしなければなりませんでした。完全性を得るには、スキーマと関係のあるすべての「生成」オーバーロードを上書きする必要があります。 –

1

、それは最初のコードではありません場合はまあ、私はこのようにそれをしようとします:

  • は、デフォルトのスキーマ内の表を生成するには、DBO

  • は、既存のデータベース

    に基づいてEDMXを生成すると言います
  • 編集TTファイルTTテンプレートを出発物質として

  • POCOと

    テイクEFおよびコンテキストのcalleに新しいプロパティを追加しますd schemaであり、生成されたクラスのクエリを強制的にデータベースオブジェクトに使用します。

このようにして、異なるスキーマのコンテキストを作成し、オブジェクトがコンテキスト間を移動できるようになります。あなたはDbContextIDbModelCacheKeyProviderを実装場合OnModelCreating

+0

これは面白いおかげだ!しかし、私の目標(上記)は単一のコンテキストでこれを行うことで、モデルを簡単に共有し、必要に応じて1回のアプリケーション実行で異なるスキーマに再接続することができます。それに加えて、私は50または100の異なるスキーマ(同じモデルを持つALL)を持つかもしれないので、私は新しいテナント(スキーマ)をプロビジョニングするたびに新しいコンテキストを生成したくありません。あなたのソリューションは本当にうまくいくはずですが、私はいくつかのスキーマしか持っていませんでした! –

+0

@KevinRadcliffeま​​あ、ほとんどの場合、単位作業パターンを実装し、長時間生きることは想定されていないので、あなたはかなり頻繁にコンテキストを再作成します。 – vittore

+0

@KevinRadcliffeそして、スキーマについて - あなたは別途にマイグレーションを管理しているので、スキーマスクリプトの作成のように、私はそれを別にします。 – vittore

2

とても素敵なアプローチと、それはよりまっすぐ進むの解決策を得るために私を助けました。 あなたが唯一の名前とメソッドをオーバーライドし、それはすべてのライターで使用されている....新しい回答のため申し訳ありません が、私はコメントすることはできませんよ....へ

public class SqlServerSchemaAwareMigrationSqlGenerator:SqlServerMigrationSqlGenerator 
{ 

    private string _schema; 

    public accountMigrationSqlGenerator(string schema) 
    { 
     _schema = schema; 
    } 

    protected override string Name(string name) 
    { 

     int p = name.IndexOf('.'); 
     if(p>0) 
     { 
      name = name.Substring(p + 1); 
     } 

     return $"[{_schema}].[{name}]"; 

    } 

} 
+0

残念ながら、すべての関数がNameプロパティを使用しているわけではありません。たとえば、RenameIndexOperationのGenerateはそれを無視します。したがって、すべてのGenerateメソッドをオーバーライドする方が安全です。 – Anatoliy

1

おかげで可能性がありますTobiasを!あなたは年に私を保存... EF 6下のOracle DB用

マイ修正:

public class IntegrationDbContext : DbContext, IDbModelCacheKeyProvider 
{ 
    private static readonly ILog __log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

    /// <summary> 
    /// Factory method 
    /// </summary> 
    public static IntegrationDbContext Create(string connectionStringName) 
    { 
     return new IntegrationDbContext(connectionStringName, GetDBSchema(connectionStringName)); 
    } 

    /// <summary> 
    /// Constructor 
    /// </summary> 
    public IntegrationDbContext() 
    { 
     Database.SetInitializer<IntegrationDbContext>(null); 
    } 

    /// <summary> 
    /// Constructor 
    /// </summary> 
    internal IntegrationDbContext(string connectionString, string schemaName) 
     : base("name={0}".Fill(connectionString)) 
    { 
     Database.SetInitializer<IntegrationDbContext>(null); 
     SchemaName = schemaName; 
    } 

    /// <summary> 
    /// DB schema name 
    /// </summary> 
    public string SchemaName { get; private set; } 

    #region Tables 
    /// <summary> 
    /// Integration table "SYNC_BONUS_DISTRIBUTION" 
    /// </summary> 
    public virtual DbSet<SYNC_BONUS_DISTRIBUTION> SYNC_BONUS_DISTRIBUTION { get; set; } 

    /// <summary> 
    /// Integration table "SYNC_MESSAGE_DISTRIBUTION" 
    /// </summary> 
    public virtual DbSet<SYNC_MESSAGE_DISTRIBUTION> SYNC_MESSAGE_DISTRIBUTION { get; set; } 

    /// <summary> 
    /// Integration table "IMPORT_TEMPLATES" 
    /// </summary> 
    public virtual DbSet<IMPORT_TEMPLATE> IMPORT_TEMPLATES { get; set; } 

    #endregion //Tables 

    private static Dictionary<string, string> __schemaCache = new Dictionary<string, string>(); 
    private static object __schCacheLock = new object(); 
    /// <summary> 
    /// Gets DB schema name from connection string, or default from config 
    /// </summary> 
    private static string GetDBSchema(string connectionStringName) 
    { 
     string result; 
     if (!__schemaCache.TryGetValue(connectionStringName, out result)) 
     { 
      lock (__schCacheLock) 
      { 
       if (!__schemaCache.TryGetValue(connectionStringName, out result)) 
       { 
        DbConnectionStringBuilder builder = new DbConnectionStringBuilder(); 
        builder.ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; 
        result = builder.ContainsKey("User ID") ? builder["User ID"] as string : ConfigurationManager.AppSettings["DefaultIntegrationSchema"]; 
        __schemaCache.Add(connectionStringName, result); 
       } 
      } 
     } 
     return result; 
    } 

    /// <summary> 
    /// Context initialization 
    /// </summary> 
    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     __log.DebugFormat("OnModelCreating for integration model in schema: {0}", SchemaName); 
     if (SchemaName != null) 
     { 
      modelBuilder.HasDefaultSchema(SchemaName); 
     } 
     //### CLOB settings 
     modelBuilder.Properties().Where(p => p.PropertyType == typeof(string) && 
              p.GetCustomAttributes(typeof(MaxLengthAttribute), false).Length == 0) 
               .Configure(p => p.HasMaxLength(2000)); 

     base.OnModelCreating(modelBuilder); 
    } 

    /// <summary> 
    /// Implementation of <see cref="IDbModelCacheKeyProvider.CacheKey"/> - thanks by this is 'OnModelCreating' calling for each specific schema. 
    /// </summary> 
    public string CacheKey 
    { 
     get { return SchemaName; } 
    } 
} 
関連する問題