entity-framework ef-code-first entity-framework-5 ef-migrations

entity framework - Migraciones del código EF5 primero: error "Los nombres de columna en cada tabla deben ser únicos" después de usar RenameColumn



entity-framework ef-code-first (1)

Estamos utilizando Entity Framework 5.0 Code First y Automatic Migrations.

Tuve una clase como esta:

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

Entonces nos dimos cuenta de que estas propiedades no eran realmente los nombres correctos, así que las cambiamos:

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

El cambio de nombre se reformuló correctamente en todo el proyecto, pero sé que las Migraciones automáticas no son lo suficientemente inteligentes como para seleccionar estos nombres explícitos en el IDE, así que primero verifiqué para verificar que la única migración pendiente era el cambio de nombre de esta columna:

update-database -f -script

Por supuesto, solo mostró el SQL bajando a Alto y Bajo y agregando Izquierda y Superior. Luego agregué una migración manual:

add-migration RenameColumns_TraversalZones_LowHigh_LeftTop

Y arreglado el código generado para simplemente:

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

Luego actualicé la db:

update-database -verbose

Y obtuve 2 nombres de columna, justo como esperaba.

Después de varias migraciones, realicé una copia de seguridad de Producción y la restauré en una base de datos local para probar el código en esta base de datos. Esta base de datos tenía ya creada la tabla TraversalZones, con los nombres antiguos de columna (Bajo y Alto) que, por supuesto, comencé a actualizar:

update-database -f -verbose

Y los comandos de cambio de nombre aparecieron en la salida, todos aparecieron bien:

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]

Luego ejecuté mi código, y se produjo un error diciéndome que la base de datos había cambiado desde la última ejecución, y que debería ejecutar la update-database ...

Así que lo corrí de nuevo:

update-database -f -verbose

Y ahora estoy atascado en este error:

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.​

Por lo tanto, claramente las Migraciones se confunden en cuanto a si la columna "Izquierda" aún debe ingresar en esta tabla; Supongo que RenameColumn dejaría las cosas en el estado adecuado, pero parece que no lo ha hecho.

Cuando vuelco lo que está intentando hacer con una update-database -f -script , lo consigo tratando de hacer exactamente lo que habría hecho si la migración manual no estuviera allí:

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'')

Esto parece ser un error en las migraciones.


La solución, obviamente, es esta:

update-database -f -script

Que puedes ver los resultados de en mi pregunta. Luego tiré todo desde el script, pero la última línea, y lo ejecuté contra la base de datos para que las Migraciones lo supieran: Ya cambiamos el nombre de esa columna, la eliminamos.

Ahora puedo continuar con esta copia de la base de datos, pero me preocupa que cada migración contra copias de Production (hasta que se haya migrado Production) siga teniendo este problema. ¿Cómo puedo resolver esto correctamente sin esta solución?

Actualizar

De hecho, esto fue un problema en todos los demás casos, incluida la producción. La solución sucia era generar un script SQL ( update-database -f -script ), después de confirmar la versión generada y la versión corregida.

Una solución un poco más limpia es tomar el SQL del script, agregar una migración manual y cambiar el contenido de Hasta simplemente:

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

Esto asegurará que otros entornos que ejecutan esta migración lo hagan exactamente de la manera que pretendía.

Probar esto es un poco complicado, así que puedes enfocarlo de esta manera:

  1. Copia de seguridad de su db por si acaso.
  2. Ejecutar el SQL. Si funciona correctamente, establezca el SQL a un lado.
  3. Agregue la migración manual y elimine todo en el método Up (). Dejarlo completamente vacío.
  4. Ejecutar update-database -f
  5. Ahora modifique el método Up () agregando el Sql("..."); llamando al SQL que has dejado de lado.

Ahora su base de datos está actualizada sin ejecutar el SQL dos veces, y otros entornos obtienen los resultados de ese SQL.