簡體   English   中英

是否有一種擴展Code-First遷移的好方法

[英]Is there a good way to extend the Code-First Migrations

我正在開始一個使用Entity Framework的新項目。 我已經研究了如何創建數據庫的選項,並發現Code-First Migrations最有意義(如果您需要知道原因,請參閱底部)。 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.訪問它們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似乎是一個贏家,因為[代碼優先]模型驅動的更改是自動的,這意味着只有一件事需要維護。

雖然我不確定它是否好,但我找到了解決方案。 我不得不在兔子洞的下方走得比我想要的更遠,而且它不是真正的延伸點。

它允許我編寫如下語句:

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

不是通用解決方案,但您可以從抽象/接口類繼承。 鑒於這需要一些代碼更改,但它相當干凈。

我已經使用這種模式為所有表定義了我的審計列(UpdatedBy,UpdateDate等)。

為了捎帶ravi所說的,你可以擴展DbMigration類:

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

public abstract class ExtendedDbMigration : DbMigration
{
    public void DoCommonTask(string parameter)
    {
        Sql("** DO SOMETHING HERE **");
    }

    public void UndoCommonTask(string parameter)
    {
        Sql("** DO SOMETHING HERE **");
    }
}

然后,在創建遷移時,將其從DbMigration更改為ExtendedDbMigration

using System.Data.Entity.Migrations;

public partial class some_migration : ExtendedDbMigration
{
    public override void Up()
    {
        DoCommonTask("Up");
    }

    public override void Down()
    {
        UndoCommonTask("Down");
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM