Есть ли хороший способ продлить первые кодовые миграции?

Я запускаю новый проект, который использует Entity Framework. Я изучил мои варианты создания базы данных и нашел, что Code-First Migrations наиболее удобны (см. Ниже, если вам нужно знать, почему). Code-First Migrations позволяет мне опуститься до произвольного SQL, что я все еще имею полный контроль. На практике я обнаружил, что проблема заключается в том, что отказ от SQL кажется чересчур повторяющимся для некоторых общих задач.

В моих целях мне все равно, что расширения в миграции не являются агностиками-провайдерами (SQL-я не подстраиваюсь). Тем не менее, я действительно не нахожу хороший шов или точку расширения в структуре миграции для добавления таких вещей.

Чтобы дать конкретный пример, предположим, что я хочу указать столбец RowGuid для репликации MS-SQL. Каждое событие имеет вид

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, по-видимому, был победителем, потому что изменения в моделях с кодовым управлением были автоматическими, и это означало, что поддерживать только одну вещь.

Ответ 1

Я нашел решение, хотя не уверен, что это хорошо. Мне пришлось идти немного дальше по кроличьей дыре, чем я хотел ее получить, и на самом деле это не точка расширения.

Это позволяет мне писать такие заявления, как:

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

Ответ 2

Не общее решение, но вы можете наследовать от абстрактного/интерфейса. Учитывая это, потребуются некоторые изменения кода, но его разумно чистые.

Я использовал этот шаблон для определения моих столбцов аудита (UpdateBy, UpdateDate и т.д.) для всех таблиц.

Ответ 3

Для контрейлерных на то, что Рави сказал, вы могли бы расширить 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");
    }
}