Migraciones en Entity Framework en un entorno colaborativo

Tenemos varios desarrolladores trabajando en un proyecto que usa Entity Framework 5.0. Cada desarrollador usa su propia base de datos SQL 2012 local para que pueda desarrollar y probar sin impedir a otros.

Al principio, usamos un híbrido de migraciones automáticas y migraciones basadas en código. Eso no funcionó del todo, así que decidimos deshabilitar las migraciones automáticas y solo permitir el uso de código. Debo añadir que comenzamos de nuevo con una base de datos limpia sin un _MigrationsHistory ‘corrupto’ de todas las migraciones automáticas.

Entonces, ahora el flujo de trabajo es:

  1. El desarrollador cambia su modelo de datos
  2. Does add-migration y lo aplica a su base de datos con la base de datos de update-database .
  3. Comprueba el cambio del modelo de datos y la migración a Git.
  4. Otro desarrollador tira, recibe los cambios y los aplica a su base de datos.

Hasta ahora, esto funcionó bien. Sin embargo, antes de hoy solía ser yo quien hacía las migraciones y los demás las aplicaban. Pero hoy hubo migraciones de tres desarrolladores. Acabo de sacar esas migraciones, hice una update-database que salió bien.

También tuve un cambio en mi propio modelo de datos, pero al final de la update-database me avisó que aún no estaba actualizado, así que add-migration . Sin embargo, cuando andamó la migración, me dio los cambios de todas las migraciones que ya había aplicado a la base de datos. Por lo tanto, intentó eliminar las columnas que ya se habían eliminado, intentó crear una tabla que ya existía, etc.

¿Como puede ser? Mi suposición era que EF solo verificaría la tabla _MigrationsHistory y descubriría qué migraciones no estaban presentes en la tabla y las aplicaría ordenadas por la marca de tiempo que es parte del nombre. Pero aparentemente no, porque incluso cuando deshago mis propios cambios y tengo un entorno limpio, todavía se queja de que mi base de datos no está sincronizada con el modelo. Pero simplemente hice esos cambios y los apliqué a mi base de datos. Está en sincronía. También puedo ver las migraciones que acabo de aplicar en la tabla _MigrationsHistory .

Lo único que puedo pensar es que agregué una propiedad a un modelo de datos que no daría como resultado un cambio en la base de datos (agregué una List al modelo de datos Y donde X es el número en la relación de uno a muchos. Esto no daría como resultado un cambio en la base de datos ya que X ya tenía una clave foránea en Y). ¿Podría ser eso? Si es así, eso es realmente frágil porque no hay forma de agregar una migración para eso ya que no hay cambio en la base de datos y tampoco estoy seguro de cómo solucionarlo.

No estoy seguro de cómo lidiar con esto, porque puedo, por supuesto, simplemente editar lo que se andamó y eliminar todo lo que ya se ha aplicado a mi base de datos. ¿Pero entonces, qué? Lo reviso y luego otro desarrollador recibe el mismo mensaje que su base de datos no está actualizada incluso después de aplicar mis nuevos cambios, andamios de sus propios cambios, obtiene el mismo andamiaje sin sentido, lo edita, lo revisa y luego el siguiente desarrollador lo consigue. Se convierte en un círculo vicioso y similar a lo que teníamos cuando usábamos migraciones automáticas y pensé que lo habíamos solucionado al cambiar a código solo. No puedo confiar ahora mismo en hacer lo correcto y es una pesadilla trabajar así.

Lo que también probé es agregar las migraciones que saqué de mis compañeros de trabajo uno a uno con update-database -t:201211091112102_ pero fue en vano. Todavía me da el andamio erróneo.

Entonces, ¿qué hicimos mal aquí, o EF simplemente no está diseñado para una colaboración como esta?

ACTUALIZAR

Creé un caso de prueba reproducible, aunque es un baile bastante largo para simular este escenario de múltiples usuarios / bases de datos múltiples.

https://github.com/JulianR/EfMigrationsTest/

Pasos para reproducir cuando tiene el proyecto anterior (estos pasos también están presentes en el código):

  1. add-migration Init
  2. actualización-base de datos (en la base de datos ‘TestDb’)
  3. Cambiar cadena de conexión para apuntar a TestDb1
  4. actualización-base de datos en TestDb1
  5. Descomentar propiedad Foo en prueba de clase
  6. add-migration M1 para agregar la propiedad Foo a TestDb1
  7. Comenta Test.Foo nuevamente
  8. Cambiar cadena de conexión para apuntar a TestDb2
  9. Excluya la migración M1 del proyecto para que no se aplique a TestDb2
  10. Descomentar propiedad Bar en clase Prueba
  11. actualización-base de datos para aplicar la migración de Init a TestDb2
  12. add-migration M2 para agregar propiedad Bar a TestDb2
  13. Cambie la cadena de conexión para que apunte al TestDb original de nuevo
  14. Incluya la migración M1 en el proyecto nuevamente
  15. Descomentar propiedad Foo en prueba de clase
  16. Descomentar la propiedad SomeInt on class Test
  17. actualizar base de datos
  18. add-migration M3
  19. actualización-base de datos, recibe un error porque M3 intenta agregar la columna Foo a la base de datos TestDb que ya fue agregada por la migración M1.

Lo anterior es para simular tres usuarios, donde el usuario 1 ingresa a su base de datos, los otros dos usan su inicialización para crear su base de datos también. Luego, el usuario 2 y el usuario 3 realizan su propio cambio en el modelo de datos y lo agregan al control de fuente junto con las migraciones necesarias para aplicar los cambios. Luego, el usuario 1 extrae los cambios del usuario 2 y 3, mientras que el usuario 1 también ha realizado un cambio en la base de datos. Luego el usuario 1 llama update-database para aplicar los cambios del usuario 2 y 3. A continuación, andamia su propia migración, que luego erróneamente agrega un cambio del usuario 2 o 3 a la migración con scaffolded que causa un error cuando se aplica a la base de datos del usuario 1.

Debe resolver manualmente los conflictos de migración tal como lo haría con los conflictos de código. Si actualiza y hay nuevas migraciones, debe asegurarse de que los metadatos detrás de la última migración coincidan con el modelo actual. Para actualizar los metadatos de la migración, vuelva a emitir el comando Agregar-Migración para ello.

Por ejemplo, antes del paso 17 (Actualizar-Base de datos) en su escenario, debe emitir el siguiente comando

 Add-Migration M2 

Esto actualizará los metadatos para ponerlos en sincronización con su modelo actual. Ahora, cuando intente agregar M3, debería estar en blanco, ya que no ha realizado ningún otro cambio de modelo.

Debe agregar una migración de “combinación” en blanco que restablecerá la instantánea de la migración más reciente en el archivo .resx. Haga esto usando el interruptor IgnoreChanges:

Add-Migration -IgnoreChanges

Vea aquí para una explicación

Opción 1: agregue una migración de ‘combinación’ en blanco

  1. Asegúrese de que cualquier cambio de modelo pendiente en su base de código local se haya escrito en una migración. Este paso garantiza que no se perderá ningún cambio legítimo cuando llegue el momento de generar la migración en blanco.
  2. Sincronización con el control de fuente.
  3. Ejecute Update-Database para aplicar las migraciones nuevas que otros desarrolladores hayan registrado. ** Nota: **** si no recibe ninguna advertencia del comando Update-Database, entonces no hubo nuevas migraciones de otros desarrolladores y no hay no es necesario realizar ninguna fusión adicional.
  4. Ejecutar Add-Migration -IgnoreChanges (por ejemplo, Add-Migration Merge -IgnoreChanges). Esto genera una migración con todos los metadatos (incluida una instantánea del modelo actual) pero ignorará los cambios que detecte al comparar el modelo actual con la instantánea en las últimas migraciones (lo que significa que obtienes un método de arriba y abajo en blanco).
  5. Continuar desarrollando o enviar a control de origen (después de ejecutar las pruebas unitarias por supuesto).

Opción 2: Actualice la instantánea del modelo en la última migración

  1. Asegúrese de que cualquier cambio de modelo pendiente en su base de código local se haya escrito en una migración. Este paso garantiza que no se perderá ningún cambio legítimo cuando llegue el momento de generar la migración en blanco.
  2. Sincronización con el control de fuente.
  3. Ejecute Update-Database para aplicar las migraciones nuevas que otros desarrolladores hayan registrado. ** Nota: **** si no recibe ninguna advertencia del comando Update-Database, entonces no hubo nuevas migraciones de otros desarrolladores y no hay no es necesario realizar ninguna fusión adicional.
  4. Ejecute Update-Database -TargetMigration (en el ejemplo que hemos seguido, sería Update-Database -TargetMigration AddRating). Esto hace que la base de datos regrese al estado de la segunda última migración, lo que efectivamente ‘no aplica’ la última migración de la base de datos. ** Nota: **** Este paso es necesario para que sea seguro editar los metadatos de la migración ya que los metadatos también se almacenan en la __MigrationsHistoryTable de la base de datos. Esta es la razón por la que solo debería usar esta opción si la última migración solo está en su base de código local. Si se aplicaron las últimas bases de datos a la base de datos, también debería volverlas a aplicar y volver a aplicar la última migración para actualizar los metadatos.
  5. Ejecutar Add-Migration (en el ejemplo que hemos seguido, sería algo así como Add-Migration 201311062215252_AddReaders). ** Nota: **** Debe incluir la marca de tiempo para que las migraciones sepa que desea editar la migración existente en lugar de andamiar una nueva. Esto actualizará los metadatos de la última migración para que coincida con el modelo actual. Recibirá la siguiente advertencia cuando el comando finalice, pero eso es exactamente lo que quiere. “Solo el código de diseñador para la migración ‘201311062215252_AddReader’ se reestructuró. Para volver a andamiar toda la migración, use el parámetro -Fuerza “.
  6. Ejecute Update-Database para volver a aplicar la última migración con los metadatos actualizados.
  7. Continuar desarrollando o enviar a control de origen (después de ejecutar las pruebas unitarias por supuesto).

MSDN tiene un gran artículo sobre esto. Por favor revisa esto.

Código del Entity Framework Primeras migraciones en entornos de equipo

Estamos teniendo problemas similares en nuestro entorno, esto es lo que hemos descubierto hasta ahora y cómo lo solucionamos:

Cuando tiene cambios que ha aplicado (base de datos de actualización) pero no se ha registrado, y luego recibe cambios de otro desarrollador que no tiene los cambios, aquí es donde parece que las cosas no se sincronizan. En nuestra experiencia, parece que los metadatos que se guardan para sus propios cambios sobreescriben los metadatos del otro desarrollador cuando realiza el proceso de la base de datos de actualización. El otro desarrollador no tiene sus cambios, por lo que los metadatos que se guardan ya no son un reflection real de su base de datos. Cuando EF hace una comparación después de eso, ‘piensa’ que los cambios son realmente nuevos nuevamente debido al cambio de metadatos.

Una solución alternativa, ciertamente fea, es realizar otra migración y borrar sus contenidos para que tenga los métodos vaciar () y vaciar (). Aplica esa migración y compruébalo en el control de la fuente y deja que todos se sincronicen con eso. Esto simplemente sincroniza todos los metadatos para que todos tengan todos los cambios contabilizados.

He agregado un problema en codeplex, este problema causa muchos problemas en nuestro equipo.

El enlace es https://entityframework.codeplex.com/workitem/1670

He pensado un poco en esto y espero contribuir con las diferentes opiniones y prácticas presentadas aquí.

Considera lo que representan tus migraciones locales. Cuando trabajo localmente con una base de datos de desarrollo, uso las migraciones para actualizar la base de datos de la manera más conveniente posible al agregar columnas, etc. a las tablas, agregar nuevas entidades, etc.

Entonces, Add-Migration verifica mi modelo actual (llamémoslo modelo b) a mi modelo anterior (modelo a) y genera una migración para pasar de a => b en la base de datos.

Para mí tiene muy poco sentido tratar de fusionar mis migraciones con otras migraciones, si todos tienen su propia base de datos y existe algún tipo de servidor de base de datos stage / test / dev / production en la organización. Todo esto depende de cómo lo haya configurado el equipo, pero tiene sentido aislarse mutuamente de los cambios que otras personas realizan si realmente quiere trabajar de forma distribuida.

Bueno, si trabajas distribuido y tienes alguna entidad, Persona, por ejemplo, en la que trabajas. Por alguna razón, muchas otras personas también están trabajando en ello. Entonces, agregas y eliminas propiedades en Person según sea necesario para tu historia en particular en el sprint (todos estamos trabajando ágilmente aquí, ¿verdad?), Como el número de Seguridad Social que creaste por primera vez en un entero porque no eres ese shiny y luego a una cadena, etc.

Agrega FirstName y LastName.

Luego terminaste y tienes diez migraciones extrañas hacia arriba y hacia abajo (es probable que hayas eliminado algunas de ellas mientras trabajabas porque eran pura basura) y recibes algunos cambios del repository central de Git. Guau. Su colega Bob también necesitaba algunos nombres, ¿quizás debería haber hablado el uno con el otro?

De todos modos, él ha agregado NameFirst y NameLast, supongo … entonces, ¿qué haces? Bueno, te fusionas, refactorizas, cambias para que tenga nombres más sanos … como FirstName y LastName, ejecutas tus pruebas y verificas su código, y luego presionas hacia la central.

Pero, ¿y las migraciones? Bien, ahora sería el momento de hacer una migración moviendo el repository central, o la “prueba” de la twig más específicamente, contiene una migración pequeña y agradable desde su modelo a => modelo b. Esta migración será una y única migración, no diez extrañas.

¿Ves a lo que me refiero? Estamos trabajando con pequeños y bonitos pocos y las comparaciones de ellos constituyen las migraciones reales. Entonces, no debemos fusionar las migraciones en absoluto, en mi opinión, deberíamos tener migraciones por twig o algo así.

De hecho, ¿necesitamos siquiera crear la migración en la twig después de la fusión? Sí, si esta base de datos se actualiza automáticamente, debemos hacerlo.

Otra cosa a considerar es nunca crear una migración antes de hacer una extracción desde el repository central. Esto significa que ambos obtendrán el código de migración de los otros miembros del equipo y sus cambios en el modelo antes de crear su migración.

Tengo que trabajar un poco más, esos son mis pensamientos sobre esto, al menos.

La solución que pude encontrar (al menos para 2 usuarios, no he probado 3) es:

  1. fusionar migraciones para sincronizar la base de datos de ejecución de metadatos (esto debería fallar), luego
  2. agregar base de datos y luego
  3. eliminar todo el código generado en up() métodos up() y down()

esto seguirá siendo ejecutado por la base de datos de actualizaciones, pero no hará nada, simplemente sincronizar los metadatos.

Estoy de acuerdo con @LavaEater. El núcleo del problema, parece ser, es que los andamios de migración deberían estar centralizados. ¿Quizás como parte de algún proceso de comstackción automatizado / integrado cada vez que se produce un impulso? A partir de entonces, los miembros del equipo pueden extraer las migraciones resultantes del servidor.

Esto significa que sus propios scripts de migración no deberían ser enviados al servidor.