update remove migrations force first existing enable create code automatic tfs ef-code-first code-first-migrations release-management ms-release-management

tfs - remove - Primer código de EF migraciones para implementar versión anterior



remove migration (2)

Estoy usando TFS Release Management para realizar una integración e implementación continua.

Estoy usando migrate.exe para realizar la migración de la base de datos durante la implementación, y esto funciona muy bien cuando se pasa de una versión anterior a una más nueva. Sin embargo, cuando desea implementar una versión anterior de la aplicación, se vuelve más embarrada.

Básicamente, el ensamblado que contiene sus migraciones para un contexto debe saber cómo pasar de decir versión 3 a versión 2. Normalmente, usa los ensamblajes que está a punto de implementar como origen de sus migraciones, pero en este caso, debe utilice los ensamblados ya desplegados, ya que son los únicos que saben cómo pasar de v3 a v2. (La versión 2 no tiene idea de que v3 incluso exista).

Mi plan actual es comparar de alguna manera los dos ensamblajes durante la implementación. Si el ensamblado en el directorio de instalación contiene migraciones "más nuevas" que la del director de despliegue, primero tendría que obtener la migración disponible "más nueva" en el ensamblado en el directorio de despliegue, y luego ejecutar:

migrate.exe AssemblyInInstallationDir /targetMigration NewestFromAssemblyInDeploymentDir

Donde, como en un escenario de implementación "normal" donde está actualizando a una versión más nueva, puede hacer:

migrate.exe AssemblyInDeploymentDir

¿Es esto un enfoque legítimo? Aún tengo que analizar el uso de las bibliotecas EF para evaluar qué migraciones hay disponibles en cada ensamblaje. También existe el desafío del hecho de que cada una de estas asambleas son las "mismas" versiones diferentes. Probablemente tendré que cargarlos en dominios de aplicaciones separados y luego usar las comunicaciones de dominio entre aplicaciones para obtener la información que necesito.

EDITAR

Creé una aplicación de prueba de concepto que me permite listar las migraciones disponibles en dos versiones diferentes del mismo ensamblaje. Esto fue fundamental para todo este proceso, así que pensé que vale la pena documentarlo.

La aplicación utiliza la reflexión para cargar cada uno de los ensamblajes y luego usa la clase DbMigrator de System.Data.Entity.Migrations para enumerar los metadatos de migración. Los nombres de las migraciones tienen como prefijo la información de la marca de tiempo, lo que me permite ordenarlos y ver qué ensamblaje contiene el conjunto de migraciones "más nuevo".

static void Main(string[] args) { const string dllName = "Test.Data.dll"; var assemblyCurrent = Assembly.LoadFile(Path.Combine(System.Environment.CurrentDirectory, string.Format("Current//{0}", dllName))); var assemblyTarget = Assembly.LoadFile(Path.Combine(System.Environment.CurrentDirectory, string.Format("Target//{0}", dllName))); Console.WriteLine("Curent Version: " + assemblyCurrent.FullName); Console.WriteLine("Target Version: " + assemblyTarget.FullName); const string contextName = "Test.Data.TestContext"; const string migrationsNamespace = "Test.Data.Migrations"; var currentContext = assemblyCurrent.CreateInstance(contextName); var targetContext = assemblyTarget.CreateInstance(contextName); var currentContextConfig = new DbMigrationsConfiguration { MigrationsAssembly = assemblyCurrent, ContextType = currentContext.GetType(), MigrationsNamespace = migrationsNamespace }; var targetContextConfig = new DbMigrationsConfiguration { MigrationsAssembly = assemblyTarget, ContextType = targetContext.GetType(), MigrationsNamespace = migrationsNamespace }; var migrator = new DbMigrator(currentContextConfig); var localMigrations = migrator.GetLocalMigrations(); //all migrations Console.WriteLine("Current Context Migrations:"); foreach (var m in localMigrations) { Console.WriteLine("/t{0}", m); } migrator = new DbMigrator(targetContextConfig); localMigrations = migrator.GetLocalMigrations(); //all migrations Console.WriteLine("Target Context Migrations:"); foreach (var m in localMigrations) { Console.WriteLine("/t{0}", m); } Console.ReadKey(); }

}

El resultado de la aplicación se ve así:

Curent Version: Test.Data, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null Target Version: Test.Data, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null Current Context Migrations: 201403171700348_InitalCreate 201403171701519_AddedAddresInfoToCustomer 201403171718277_RemovedStateEntity 201403171754275_MoveAddressInformationIntoContactInfo 201403181559219_NotSureWhatIChanged 201403181731525_AddedRowVersionToDomainObjectBase Target Context Migrations: 201403171700348_InitalCreate 201403171701519_AddedAddresInfoToCustomer 201403171718277_RemovedStateEntity


En realidad, resolvimos este problema y hemos estado usando nuestras herramientas durante más de un año para realizar implementaciones de bases de datos completamente continuas en la producción. No hay humanos involucrados. :)

Hemos hecho un poco de este público en GitHub: https://github.com/GalenHealthcare/Galen.Ef.Deployer

Puede hacer cambios de "interrupción", pero en general, también evitamos eso, pero sobre todo porque nuestras aplicaciones se mantienen activas durante las actualizaciones. Tratamos el nivel de datos como un componente de implementación independiente y, como resultado, tiene una "interfaz" que debe seguir siendo compatible.

A menudo usaremos un enfoque de actualización de fases múltiples donde implementamos una versión intermedia que es compatible con versiones anteriores y posteriores, actualizamos nuestros diversos servicios de aplicaciones y finalmente mejoramos el nivel de la base de datos para eliminar la compatibilidad existente.

Incluso en ese escenario, tenemos la capacidad de ir automáticamente de / a cualquier versión de nuestro esquema y datos. De hecho, hemos agregado pruebas unitarias que verifican esto cada vez que compilamos para cada versión de base de datos. Básicamente recorre la cadena de iteraciones de esquema y valida que las migraciones ascendentes y descendentes siempre funcionan y mantienen la consistencia y la compatibilidad de los datos. Puedes ver estas pruebas en el proyecto GitHub. Aquí hay un ejemplo:

https://github.com/GalenHealthcare/Galen.Ef.Deployer/blob/master/Galen.Ci.EntityFramework.Deployer/Galen.Ci.EntityFramework.Testing/MigrationTestRunner.cs


La forma en que generalmente me acerco a esto es (casi) nunca hacer cambios bruscos en el esquema de mi base de datos. Básicamente es una forma controlada de deuda técnica.

Por ejemplo, digamos que estoy reemplazando ColumnX con ColumnY. El enfoque típico es "copiar todos los datos de ColumnX a ColumnY, eliminar ColumnX del esquema". Esto mata su capacidad para retroceder a la versión anterior, porque ColumnX se ha ido.

La forma más sencilla de deshacer esto es agregar ColumnY, copiar los datos y agregar desencadenantes para mantener ambas columnas sincronizadas entre sí. ¡Esto no es un estado permanente! Una historia de usuario para "Eliminar ColumnX y desencadenadores asociados" va inmediatamente a la acumulación, para una futura iteración, cuando estamos seguros de que nunca volveremos a una versión que depende de ColumnX.

La reversión aún puede implicar la publicación de la versión anterior de DACPAC, con la advertencia de que debe asegurarse de no colocar elementos presentes en la base de datos que no estén en el esquema. De esta forma, si actualizó una serie de procedimientos almacenados para extraerlos de ColumnY, puede publicar la versión anterior que extrae de ColumnX y la versión anterior ignora que el esquema ha cambiado.