EF5 Code First Migrations: "Имена столбцов в каждой таблице должны быть уникальными" после использования RenameColumn - программирование
Подтвердить что ты не робот

EF5 Code First Migrations: "Имена столбцов в каждой таблице должны быть уникальными" после использования RenameColumn

Мы используем First Entity Framework 5.0 и автоматическую миграцию.

У меня был класс вроде:

public class TraversalZones
{
    public int Low { get; set; }
    public int High { get; set; }
}​

Тогда мы поняли, что эти свойства на самом деле не являются правильными именами, поэтому мы изменили их:

public class TraversalZones
{
    public int Left { get; set; }
    public int Top { get; set; }
}​

Переименовать рефакторинг правильно во всем проекте, но я знаю, что автоматические миграции не достаточно умны, чтобы забрать эти явные переименования в среде IDE, поэтому я сначала проверил, чтобы проверить, что только ожидающая миграция была переименована в этом столбце:

update-database -f -script

Конечно, это просто показало, что SQL понижает Low и High и добавляет Left и Top. Затем я добавил миграцию вручную:

add-migration RenameColumns_TraversalZones_LowHigh_LeftTop

И скорректированный сгенерированный код просто:

public override void Up()
{
    RenameColumn("TraversalZones", "Low", "Left");
    RenameColumn("TraversalZones", "High", "Top");
}

public override void Down()
{
    RenameColumn("TraversalZones", "Left", "Low");
    RenameColumn("TraversalZones", "Top", "High");
}

Затем я обновил db:

update-database -verbose

И получил 2 переименования столбцов, как и ожидал.

Несколько миграций позже я создал резервную копию Production и восстановил ее в локальной базе данных, чтобы протестировать код в этой БД. Эта БД имела уже созданную в ней таблицу TraversalZones со старыми именами столбцов (Low и High), я, конечно же, начал с ее обновления:

update-database -f -verbose

И команды переименования появились на выходе - все получилось хорошо:

EXECUTE sp_rename @objname = N'TraversalZones.Low', @newname = N'Left', @objtype = N'COLUMN'
EXECUTE sp_rename @objname = N'TraversalZones.High', @newname = N'Top', @objtype = N'COLUMN'
[Inserting migration history record]

Затем я запустил свой код, и он ошибся, указав, что база данных изменилась с момента последнего запуска, и что я должен запустить update-database....

Итак, я снова запустил его:

update-database -f -verbose

И вот теперь эта ошибка:

No pending code-based migrations. Applying automatic migration:
201212191601545_AutomaticMigration.
ALTER TABLE [dbo].[TraversalZones] ADD [Left] [int] NOT NULL DEFAULT 0
System.Data.SqlClient.SqlException (0x80131904): Column names in each table must be unique. Column name 'Left' in table 'dbo.TraversalZones' is specified more than once.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements)
   at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, XDocument targetModel, IEnumerable`1 operations, Boolean downgrading, Boolean auto)
   at System.Data.Entity.Migrations.DbMigrator.AutoMigrate(String migrationId, XDocument sourceModel, XDocument targetModel, Boolean downgrading)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.AutoMigrate(String migrationId, XDocument sourceModel, XDocument targetModel, Boolean downgrading)
   at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
   at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
   at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
   at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
   at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
ClientConnectionId:c40408ee-def3-4553-a9fb-195366a05fff
Column names in each table must be unique. Column name 'Left' in table 'dbo.TraversalZones' is specified more than once.​

Итак, очевидно, что Миграции смущены относительно того, должен ли столбец "Слева" поместить его в эту таблицу; Я бы предположил, что RenameColumn оставит вещи в правильном состоянии, но, похоже, это не так.

Когда я сбрасываю то, что он пытается сделать с update-database -f -script, я пытаюсь сделать то, что он сделал бы, если бы миграция вручную не была:

ALTER TABLE [dbo].[TraversalZones] ADD [Left] [int] NOT NULL DEFAULT 0
ALTER TABLE [dbo].[TraversalZones] ADD [Top] [int] NOT NULL DEFAULT 0
DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'dbo.TraversalZones')
AND col_name(parent_object_id, parent_column_id) = 'Low';
IF @var0 IS NOT NULL
    EXECUTE('ALTER TABLE [dbo].[TraversalZones] DROP CONSTRAINT ' + @var0)
ALTER TABLE [dbo].[TraversalZones] DROP COLUMN [Low]
DECLARE @var1 nvarchar(128)
SELECT @var1 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'dbo.TraversalZones')
AND col_name(parent_object_id, parent_column_id) = 'High';
IF @var1 IS NOT NULL
    EXECUTE('ALTER TABLE [dbo].[TraversalZones] DROP CONSTRAINT ' + @var1)
ALTER TABLE [dbo].[TraversalZones] DROP COLUMN [High]
INSERT INTO [__MigrationHistory] ([MigrationId], [Model], [ProductVersion]) VALUES ('201212191639471_AutomaticMigration', 0x1F8B08000...000, '5.0.0.net40')

Это кажется ошибкой в ​​Migrations.

4b9b3361

Ответ 1

Обходной путь, очевидно, таков:

update-database -f -script

Что вы можете увидеть в моем вопросе. Затем я выбросил все из script, но в последнюю строку, и запустил это для БД, чтобы сообщить Migrations: Мы уже переименовали этот столбец, отключили его.

Теперь я могу продолжить работу с этой копией базы данных, но я обеспокоен тем, что всякая миграция с копиями Production (пока сама передача не была перенесена) будет продолжать эту проблему. Как я могу разрешить это правильно без этого обхода?

Обновление

Это была проблема в каждом другом экземпляре, включая Production. Грязное решение заключалось в создании SQL script (update-database -f -script) после выполнения сгенерированной версии и фиксированной версии.

Немного более чистое решение - взять SQL из script, добавить ручную миграцию и изменить содержимое до простого:

public void Up()
{
    Sql("...That SQL you extracted from the script...");
}

Это обеспечит, чтобы другие среды, выполняющие эту миграцию, выполняли именно так, как вы планировали.

Тестирование это немного сложно, поэтому вы можете подойти так:

  • Резервное копирование вашего db на всякий случай.
  • Запустите SQL. Если он работает правильно, отложите SQL.
  • Добавьте ручную миграцию и уничтожьте все в методе Up(). Оставьте его полностью пустым.
  • Запустить update-database -f
  • Теперь измените метод Up(), добавив Sql("..."); вызов SQL, который вы отложили.

Теперь ваш db обновляется без запуска SQL дважды, а другие среды получают результаты этого SQL.