9

我開始一個使用實體框架的新項目。我研究瞭如何創建數據庫的選項,並發現Code-First Migrations最有意義(如果需要知道原因,請參閱底部)。代碼優先遷移讓我下降到任意的SQL意味着我仍然有完全的控制權。在實踐中,我發現這個問題是,對於一些常見任務來說,下降到SQL似乎是非常重複的。有沒有擴展代碼優先遷移的好方法

出於我的目的,我不在乎遷移中的擴展是與提供者無關(我不在內部的SQL)。但是,我並不是真的在遷移框架中找到添加此類事物的良好接口或擴展點。

舉一個具體的例子,假設我想爲MS-SQL複製指定一個RowGuid列。每一次出現需要的

Sql(
    string.Format(
     "Alter Table {0} Alter Column {1} Add ROWGUIDCOL", 
     table, 
     column)); 

所以我寫靜態方法的形式擺脫了一些冗餘的

Sql(MigrationHelper.SetRowGuid(table, column); 

- 或 -

MigrationHelper.SetRowGuid(Sql, table, column); //passing the Sql method 

也許可以使那些既延長DbMigration上的方法,並通過this.訪問它們但是,這仍然看起來不合適:

CreateTable(
    "dbo.CustomerDirectory", 
    c => new 
     { 
      Uid = c.Int(nullable: false), 
      CustomerUid = c.Int(nullable: false), 
      Description = c.String(nullable: false, maxLength: 50, unicode: false), 
      RowGuid = c.Guid(nullable: false), 
     }) 
    .PrimaryKey(t => t.Uid) 
    .ForeignKey("dbo.Customer", t => t.CustomerUid); 

this.SetRowGuid(Sql, "dbo.CustomerDirectory", "RowGuid"); 
//Custom method here because of desired naming convention of Constraint 
this.SetDefaultConstraint(Sql, "dbo.CustomerDirectory", "''"): 

這不是很糟糕,但它仍然感覺像對我來說是一個黑客。我必須重複表名,並且我需要確保生成的列名稱正確。我發現表名需要重複很多,但列也是如此。然而,我真的試圖添加到表名和列名都已知的情況下發生的表聲明。

但是,我無法找到一個很好的擴展點來擴展流暢的接口,或者以一種感覺一致的方式擴展代碼第一次遷移。我錯過了什麼嗎?有沒有人找到一個這樣做的好方法?

一些理由,爲什麼我在這種情況下:

我不喜歡的似乎像使用普通自定義的通用解決方案屬性的解決方案表明,有幾個原因非映射數據庫,但最強烈因爲它們不會被遷移自動拾取,這意味着額外的維護。模型優先的解決方案已經不存在了,因爲它們不能完全控制數據庫。數據庫 - 首先因爲控制而吸引人;但是,它並沒有Code-First Migrations提供的開箱即用變更管理功能。因此,Code-First Migrations似乎是一個贏家,因爲[code-first]模型驅動的更改是自動的,這意味着只有一件事情需要維護。

+0

+1突出EFCF作爲唯一的實體框架的做法,真正促進了變更控制。 – bwerks

回答

5

我找到了一個解決方案,雖然我不確定它是否好。我不得不比兔子洞更遠一點,這並不是一個延伸點。

這讓我寫語句,如:

CreateTable(
    "dbo.CustomerDirectory", 
    c => new 
     { 
      Uid = c.Int(nullable: false), 
      CustomerUid = c.Int(nullable: false), 
      Description = c.String(nullable: false, maxLength: 50, unicode: false), 
      RowGuid = c.Guid(nullable: false), 
     }) 
    .PrimaryKey(t => t.Uid) 
    .ForeignKey("dbo.Customer", t => t.CustomerUid) 
     //SqlValue is a custom static helper class 
    .DefaultConstraint(t => t.Description, SqlValue.EmptyString) 
     //This is a convention in the project 
     //Equivalent to 
     // .DefaultConstraint(t => t.RowGuid, SqlValue.EmptyString) 
     // .RowGuid(t => t.RowGuid) 
    .StandardRowGuid() 
     //For one-offs 
    .Sql(tableName => string.Format("ALTER TABLE {0} ...", tableName"); 

我不喜歡:

  • ,我反思私有成員,通常不會使用這樣的解決方案,這一事實
  • 如果使用了列定義的「name」可選參數,那麼選擇列的lambda可能會返回錯誤的列名稱。

我只在這裏使用它,因爲考慮:

  • 我們船的EF總成,使我們相信使用將具有這些成員之一。
  • 幾個單元測試會告訴我們新版本是否會打破這些。
  • 它被遷移。
  • 我們已經獲得了所有我們正在反思的信息,因此如果新版本確實破壞了這一點,我們可能會採取一些措施來替代此功能。
internal static class TableBuilderExtentions 
{ 
    internal static TableBuilder<TColumns> Sql<TColumns>(
     this TableBuilder<TColumns> tableBuilder, 
     Func<string, string> sql, 
     bool suppressTransaction = false, 
     object anonymousArguments = null) 
    { 
     string sqlStatement = sql(tableBuilder.GetTableName()); 

     DbMigration dbMigration = tableBuilder.GetDbMigration(); 
     Action<string, bool, object> executeSql = dbMigration.GetSqlMethod(); 

     executeSql(sqlStatement, suppressTransaction, anonymousArguments); 

     return tableBuilder; 
    } 

    [Pure] 
    private static DbMigration GetDbMigration<TColumns>(this TableBuilder<TColumns> tableBuilder) 
    { 
     var field = tableBuilder.GetType().GetField(
      "_migration", BindingFlags.NonPublic | BindingFlags.Instance); 
     return (DbMigration)field.GetValue(tableBuilder); 
    } 

    /// <summary> 
    /// Caution: This implementation only works on single properties. 
    /// Also, coder may have specified the 'name' parameter which would make this invalid. 
    /// </summary> 
    private static string GetPropertyName<TColumns>(Expression<Func<TColumns, object>> someObject) 
    { 
     MemberExpression e = (MemberExpression)someObject.Body; 

     return e.Member.Name; 
    } 

    [Pure] 
    private static Action<string, bool, object> GetSqlMethod(this DbMigration migration) 
    { 
     MethodInfo methodInfo = typeof(DbMigration).GetMethod(
      "Sql", BindingFlags.NonPublic | BindingFlags.Instance); 
     return (s, b, arg3) => methodInfo.Invoke(migration, new[] { s, b, arg3 }); 
    } 

    [Pure] 
    private static string GetTableName<TColumns>(this TableBuilder<TColumns> tableBuilder) 
    { 
     var field = tableBuilder.GetType().GetField(
      "_createTableOperation", BindingFlags.NonPublic | BindingFlags.Instance); 

     var createTableOperation = (CreateTableOperation)field.GetValue(tableBuilder); 
     return createTableOperation.Name; 
    } 
} 
+2

+1使用'[Pure]' – nicodemus13

0

不是一個通用的解決方案,但可以從一個抽象/接口類繼承。鑑於這將需要一些代碼更改,但其相當乾淨。

我已經使用此模式爲所有表定義我的審計列(UpdatedBy,UpdateDate等)。