0

我們在一個相對較新的項目中使用了EF 6 Code First Migrations(即沒有太多混亂的情況需要處理)。此外,由於這是一個「企業-y」的應用,我們有我們的目標數據庫的一些具體部署規則:如何使用自定義配置步驟配置EF數據庫?

  • 所有的應用程序級別的數據訪問必須通過特定的數據庫用戶(app-user
  • 此完成app-user沒有權限來創建新的數據庫

因此,爲了正確地提供這種應用一個新的目標數據庫,我們需要:

  • CREATE DATABASE [database_name] CONTAINMENT = PARTIAL
  • CREATE USER [app-user] WITH PASSWORD=N'[email protected]'
  • (加上指定特定的數據庫角色,這個新的用戶)

我希望通過編寫自定義IDatabaseInitializer<TContext>,從事這項運動,但似乎我不能最基本的數據庫在正確的點進行初始化。

概念,我想這樣做:

  • 有用於對數據庫的讀/寫訪問一個連接字符串,使用「控制器」 app-user用戶
  • 有一個單獨的連接字符串僅用於供應數據庫,使用更多的特權憑證

我曾嘗試使用的代碼看起來有點像這樣:

internal class ProvisionThenMigrateInitializer<TContext, TConfiguration> 
    : MigrateDatabaseToLatestVersion<TContext, TConfiguration>, IDatabaseInitializer<TContext> 
    where TContext : DbContext 
    where TConfiguration : DbMigrationsConfiguration<TContext>, new() 
{ 
    private readonly DbMigrationsConfiguration _readWriteConfiguration; 
    private readonly string _provisioningConnectionName; 

    public ProvisionThenMigrateInitializer(string readWriteConnectionName, string provisioningConnectionName) 
    { 
     _provisioningConnectionName = provisioningConnectionName; 

     _readWriteConfiguration = new TConfiguration 
     { 
      TargetDatabase = new DbConnectionInfo(readWriteConnectionName) 
     }; 
    } 

    void IDatabaseInitializer<TContext>.InitializeDatabase(TContext context) 
    { 
     if (context.Database.Exists()) 
     { 
      if (!context.Database.CompatibleWithModel(false)) 
      { 
       DbMigrator migrator = new DbMigrator(_readWriteConfiguration); 
       migrator.Update(); 
      } 
     } 
     else 
     { 
      // TODO - Create the DB and user here... 

      string[] sqlStatements = 
      { 
       "CREATE DATABASE [database_name] CONTAINMENT = PARTIAL ", 
       "USE [database_name]", 
       "CREATE USER [app_user] WITH PASSWORD=N'[email protected]'", 
       "USE [database_name]", 
       "ALTER ROLE [db_datareader] ADD MEMBER [app_user]", 
       "ALTER ROLE [db_datawriter] ADD MEMBER [app_user]", 
      }; 

      string connectionString = ConfigurationManager.ConnectionStrings[_provisioningConnectionName].ConnectionString; 

      SqlConnection sqlConnection = new SqlConnection(connectionString); 

      foreach (SqlCommand command in sqlStatements.Select(sqlStatement => new SqlCommand(sqlStatement, sqlConnection))) 
      { 
       command.ExecuteNonQuery(); 
      } 

      context.Database.Create(); 
      Seed(context); 
      context.SaveChanges(); 
     } 
    } 

我將在我的DbContext派生類的靜態構造函數使用初始化:

Database.SetInitializer(new ProvisionThenMigrateInitializer<Context, Configuration>(
    DOMAIN_MODEL_CONNECTION_STRING_NAME, 
    DOMAIN_MODEL_PROVISIONING_CONNECTION_STRING_NAME)); 

然而,當我試圖用我看中新的自定義數據庫初始化,通過以下方式,它只是普通的沒有按「T工作:

using (Context c = new Context()) 
{ 
    try 
    { 
     c.Database.Initialize(true); 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e); 
    } 
} 

我想,到時候我嘗試調用c.Database.Initialize(true) EF已經試圖連接到數據庫(使用app_user憑證,而不是「供應的憑證),將connecti試圖失敗,我們炸燬了。

實際上是否可以使用EF 6,Code First和Migrations來實現我的數據庫的配置?如果是這樣,我做錯了什麼?

非常感謝。

+0

你應該能夠有特殊的聯繫,做你自己。 –

回答

1

我是這樣做的:

我有一個「管理員」 SQL登錄名是「dbcreator」和「和securityadmin」固定服務器角色的成員。

我有兩個連接字符串:一個指定'admin'sql登錄名,另一個指定我爲數據庫的租戶連接保留的sql登錄名。 'tenant'登錄是通過初始遷移創建的,僅授予讀者和寫入者訪問域模型數據庫的權限。

我有一個域模型。 我有我的DbContext類。 我在我的DbContext類中有一個無參數構造函數,它指定了ADMIN連接字符串,並且用於運行遷移;我有另一個構造函數,它指定了TENANT連接字符串,並且是通過代碼使用在登錄租戶用戶的上下文中進行的所有db訪問的構造方法。 公共上下文() :基座(ADMIN_CONNECTION_STRING_NAME) { //等 和 公共上下文(INT tenantOrgId) :基座(TENANT_CONNECTION_STRING_NAME) {

之前使遷移,我用的DbContext在一個單位測試,導致EF Code First創建數據庫目錄。 我已經啓用了生成初始DbMigration的遷移。

然後我編輯的初始DbMigration「向上」的方法來提供租戶SQL登錄,並授予其會員資格的讀者和作家的角色:

public override void Up() 
    { 
     SqlConnectionStringBuilder domainModelConnectionStringBuilder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings[Context.TENANT_CONNECTION_STRING_NAME].ConnectionString); 
     string domainModelDatabaseName = domainModelConnectionStringBuilder.InitialCatalog; 

     Sql(string.Format("IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = 'gsp_domainmodel_tenant') CREATE LOGIN [gsp_domainmodel_tenant] WITH PASSWORD=N'[email protected]!', DEFAULT_DATABASE=[{0}], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF", domainModelDatabaseName)); 
     Sql(string.Format("USE [{0}]", domainModelDatabaseName)); 
     Sql(string.Format("IF NOT EXISTS (SELECT * FROM [{0}].sys.database_principals WHERE name = 'gsp_domainmodel_tenant') CREATE USER [gsp_domainmodel_tenant] FOR LOGIN [gsp_domainmodel_tenant] WITH DEFAULT_SCHEMA=[gsp]", domainModelDatabaseName)); 
     Sql(string.Format("USE [{0}]", domainModelDatabaseName)); 
     Sql(string.Format("ALTER ROLE [db_datareader] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName)); 
     Sql(string.Format("USE [{0}]", domainModelDatabaseName)); 
     Sql(string.Format("ALTER ROLE [db_datawriter] ADD MEMBER [gsp_domainmodel_tenant]", domainModelDatabaseName)); 

     CreateTable( //etc 

這就是你所需要的,如果你是幸福的做該團隊使用Update-Database將遷移應用到其本地數據庫,並且您很高興在命令行上執行Migrate.exe以在構建計算機上部署數據庫,並且您很高興將db更改部署到使用生產你自己的智慧。

您可以更進一步並指定MigrateDatabaseToLatestVersion初始化程序,以在本地開發工作站和您部署的環境中自動部署遷移。 訣竅是,您需要使用無參數DbContext構造函數使MigrateDatabaseToLatestVersion初始化器運行,以便在ADMIN sql登錄(而非TENANT)的上下文中應用遷移。這是通過以下方式實現的: static Context() { 數據庫。SetInitializer(new MigrateDatabaseToLatestVersion());

 // Make the initializer run now, with the parameterless constructor, such that the migrations are run using the admin connection string. 
     using(var initializerCtx = new Context()) 
     { 
      initializerCtx.Database.Initialize(true); 
     } 
    } 
+0

絕對!答案是在使用EF6的遷移時不要使用'IDatabaseInitializer '基礎的東西;在腳手架遷移中使用'public override void Up()'和'public override void Down()'方法。 –

0

你應該可以做你想做的。問題的關鍵是確保使用正確的連接細節訪問/更新上下文。

當它適合您的代碼時調用遷移方法。

更改MigrateDatabaseToLatestVersion以匹配您的遷移策略。

編輯:我會嘗試和總結這個想法,並顯示一個片段樣本。

本質上,我使用默認爲DONT TOUCH DB的LUW類。 Luw需要構造函數中的DBServer和DBName 我有一個工具可以獲取SQL Server的DBConnection

從管理員用戶界面我有一個按鈕。遷移。 我可以在適合時觸發自動遷移。 我目前使用自動。但是這個概念很適用於託管遷移。

public class Luw public Luw {string dataSource,string dbName){//構造函數 Context = GetContext(dataSource,dbName); }

public override void MigrateDb() { 
     // i put this method in my UoW class, I trigger Migrations when I want them to start. 
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<MYDbContext, MYSECIALMigrationConfiguration>()); 
    // Context = GetDefaultContext(); //HERE GET THE CONTEXT WITH CORRECT CONNECTION INFO 
    Context.Database.Initialize(true); 
} 


    public static MyDbContext GetContext(string dataSource, string dbName) 
    { 
     Database.SetInitializer(new ContextInitializerNone<MyDbContext>()); 
     return new MyDbContext((MYTOOLS.GetSQLConn4DBName(dataSource,dbName)),true); 
    } 

public class MYSPECIALMigrationConfiguration : MYBaseMigrationConfiguration<MYDbContext>{ } 


public abstract class MYBaseMigrationConfiguration<TContext> : DbMigrationsConfiguration<TContext> 
where TContext : DbContext{ 

protected MYBaseMigrationConfiguration() { 
    AutomaticMigrationsEnabled = true; // you can still chnage this later if you do so before triggering Update 
    AutomaticMigrationDataLossAllowed = true; // you can still chnage this later if you do so before triggering Update 

} 

public clas SQLTOOLS{ 
// ..... for SQL server.... 
public DbConnection GetSqlConn4DbName(string dataSource, string dbName) { 
     var sqlConnStringBuilder = new SqlConnectionStringBuilder(); 
     sqlConnStringBuilder.DataSource = String.IsNullOrEmpty(dataSource) ? DefaultDataSource : dataSource; 
     sqlConnStringBuilder.IntegratedSecurity = true; 
     sqlConnStringBuilder.MultipleActiveResultSets = true; 

     var sqlConnFact = new SqlConnectionFactory(sqlConnStringBuilder.ConnectionString); 
     var sqlConn = sqlConnFact.CreateConnection(dbName); 
     return sqlConn; 
    }   
+0

啊,謝謝菲爾,我會用你的建議深入研究這個... –

+0

嗨菲爾。所以,我不完全確定你的'MigrateDb()'方法屬於哪個類,以及它覆蓋哪個實現。我也不完全確定一個自定義的'DbMigrationsConfiguration'是否真的是數據庫初始化的答案。還在琢磨...... –

相關問題