11

EntityFrameworkを使用する新しいプロジェクトを開始しています。私はデータベースを作成する方法のオプションを調査し、コードファーストマイグレーションが最も理にかなっていることを発見しました(理由を知る必要がある場合は下部を参照してください)。コードファーストマイグレーションでは、任意の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-FirstMigrationsが提供するすぐに使用できる変更管理機能はありません。したがって、[コードファースト]モデル駆動型の変更は自動的に行われ、維持するものが1つしかないため、コードファースト移行が勝者のように見えました。

4

3 に答える 3

7

それが良いかどうかはわかりませんが、解決策を見つけました。うさぎの穴に行きたいと思っていたよりも少し下に行かなければなりませんでした。実際には拡張ポイントではありません。

次のようなステートメントを書くことができます。

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」オプション パラメータが使用された場合、列を選択するためのラムダが間違った列名を返す可能性があること。

ここでのみ使用することを検討しています。理由は次のとおりです。

  • 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;
    }
}
于 2012-09-14T13:23:06.037 に答える
1

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");
    }
}
于 2019-05-16T17:52:06.620 に答える
0

一般的な解決策ではありませんが、抽象/インターフェイス クラスから継承できます。これを考えると、コードの変更が必要になりますが、かなりクリーンです。

このパターンを使用して、すべてのテーブルの監査列 (UpdatedBy、UpdateDate など) を定義しました。

于 2012-09-14T06:25:43.650 に答える