Есть ли хороший способ продлить первые кодовые миграции?
Я запускаю новый проект, который использует 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");
}
}