Hola Hoy lancé una nueva versión de ThinkingHome.Migrator , una herramienta para la migración versionada de un esquema de base de datos a la plataforma .NET Core.

Paquetes publicados en NuGet , documentación detallada escrita. Ya puede usar el nuevo migrador, y le diré cómo apareció, por qué tiene la versión número 3.0.0 (aunque esta es la primera versión) y por qué es necesario cuando hay EF Migraciones y FluentMigrator .
Como empezó todo
Hace 9 años, en 2009, trabajé como desarrollador de ASP.NET. Cuando lanzamos nuestro proyecto, una persona especial se quedó despierta hasta tarde y, al mismo tiempo que actualizó los archivos en el servidor, ejecutó scripts SQL con sus manos actualizando las bases de datos en el producto. Buscamos una herramienta que hiciera esto automáticamente y encontramos el proyecto Migrator.NET .
El migrador propuso una nueva idea para ese momento: establecer cambios en la base de datos en forma de migraciones. Cada migración contiene una pequeña porción de cambios y tiene un número de versión al que irá la base de datos una vez que se complete. El migrador mismo realizó un seguimiento de las versiones y realizó las migraciones necesarias en el orden necesario. Especialmente genial fue el hecho de que el migrador permitió establecer cambios inversos para cada migración. Al inicio del migrador, era posible establecer una versión inferior a la actual, y automáticamente revertiría la base de datos a esta versión, realizando las migraciones necesarias en el orden inverso.
[Migration(1)] public class AddAddressTable : Migration { override public void Up() { Database.AddTable("Address", new Column("id", DbType.Int32, ColumnProperty.PrimaryKey), new Column("street", DbType.String, 50), new Column("city", DbType.String, 50) ); } override public void Down() { Database.RemoveTable("Address"); } }
Hubo muchos errores en ese migrador. No sabía cómo trabajar con esquemas de bases de datos que no fueran el esquema predeterminado. En algunos casos, generó consultas SQL incorrectas, y si especifica un número de versión inexistente, cae en un bucle infinito. Como resultado, mis colegas y yo bifurcamos el proyecto y solucionamos errores allí.
GitHub.com con sus tenedores y solicitudes de extracción no estaba allí entonces (el código migrante estaba en code.google.com ). Por lo tanto, especialmente no nos molestamos en asegurarnos de que nuestros cambios volvieran al proyecto original, solo vimos nuestra copia y la usamos nosotros mismos. Con el tiempo, reescribimos la mayor parte del proyecto y me convertí en su principal responsable. Luego publiqué el código de nuestro migrador en Google Code y escribí un artículo sobre Habr . Así que hubo ECM7.Migrator.
Durante el trabajo en el migrador, lo reescribimos casi por completo. Al mismo tiempo, simplificaron un poco la API y cubrieron todo con pruebas automáticas. Personalmente, realmente disfruté usando lo que sucedió. A diferencia del migrador original, había una sensación de fiabilidad y no había sensación de que ocurriera una magia extraña.
Resultó que no solo me gustaba nuestro migrador. Hasta donde yo sé, se utilizó en empresas bastante grandes. Sé sobre ABBYY, BARS Group y concert.ru. Si escribe la consulta de búsqueda "ecm7 migrator", puede ver en los resultados artículos al respecto, menciones en el currículum, descripciones de uso en el trabajo de los estudiantes. A veces recibí cartas de extraños con preguntas o palabras de agradecimiento.
Después de 2012, el proyecto casi no se desarrolló. Sus capacidades actuales cubrían todas las tareas que tenía y no veía la necesidad de terminar algo.
ThinkingHome.Migrator
El año pasado, comencé a trabajar en un proyecto en .NET Core. Allí era necesario hacer posible conectar complementos, y los complementos deberían poder crear la estructura de base de datos necesaria para ellos mismos a fin de almacenar sus datos allí. Esta es una tarea así, para la cual un migrador es muy adecuado.
Ef migraciones
Utilicé Entity Framework Core para trabajar con la base de datos, así que lo primero que probé fue EF Migrations. Desafortunadamente, casi de inmediato tuve que abandonar la idea de usarlos.
Las migraciones de Entity Framework arrastran un montón de dependencias al proyecto, y al inicio hacen algo de magia especial. Un paso a la izquierda / un paso a la derecha: te topas con restricciones. Por ejemplo, las migraciones de Entity Framework por alguna razón deben estar necesariamente en el mismo ensamblado con DbContext
. Esto significa que no será posible almacenar migraciones dentro de complementos.
Inmigrante fluido
Cuando quedó claro que EF Migrations no encajaba, busqué una solución en google y encontré varios migradores de código abierto. El más avanzado de ellos, a juzgar por la cantidad de descargas en NuGet y las estrellas en GitHub, resultó ser FluentMigrator . FM es muy bueno! Él sabe mucho y tiene una API muy conveniente. Al principio decidí que esto era lo que necesitaba, pero luego se descubrieron varios problemas.
El principal problema es que FluentMigrator no sabe cómo tener en cuenta varias secuencias de versiones dentro de la misma base de datos. Como escribí anteriormente, necesitaba usar un migrador en una aplicación modular. Es necesario que los módulos (complementos) se puedan instalar y actualizar independientemente uno del otro. FluentMigrator tiene una numeración de versión de extremo a extremo. Debido a esto, es imposible ejecutar / revertir la migración de un complemento desde la base de datos sin afectar la estructura de la base de datos de los complementos restantes.
Traté de organizar el comportamiento deseado usando etiquetas , pero esto tampoco es exactamente lo que necesita. FluentMigrator no almacena información de etiquetas para migraciones completadas. Además, las etiquetas están vinculadas a migraciones, no a ensamblajes. Esto es muy extraño, dado que el punto de entrada para la migración es exactamente el ensamblaje. En principio, probablemente era posible hacer versiones paralelas de esta manera, pero debe escribir un contenedor bastante complicado sobre el migrador.
Puerto ECM7.Migrator a .NET Core
Al principio, esta opción ni siquiera se consideró. En ese momento, la versión actual de .NET Core era 1.1 y su API era poco compatible con .NET Framework, en el que funcionaba ECM7.Migrator. Estaba seguro de que portarlo a .NET Core sería difícil y requeriría mucho tiempo. Cuando no había opciones para "tomar el acabado", decidí probarlo. La tarea fue más fácil de lo que esperaba. Sorprendentemente, funcionó casi de inmediato. Solo se requieren ediciones menores. Como la lógica del migrador estaba cubierta por pruebas, todos los lugares que se rompieron fueron visibles de inmediato y los reparé rápidamente.
Ahora he portado adaptadores para solo cuatro DBMS: MS SQL Server, PostgreSQL, MySQL, SQLite. No porté adaptadores para Oracle (ya que todavía no hay un cliente estable para .NET Core), MS SQL Server CE (ya que funciona solo en Windows y no tengo lugar para ejecutarlo) y Firebird (porque no muy popular, puerto más tarde). En principio, si necesita crear proveedores para estos u otros DBMS, esto es bastante simple.
El código fuente para el nuevo migrador está en GitHub . Configurado el lanzamiento de pruebas para cada DBMS en Travis CI. Se escribió una utilidad de línea de comandos ( .NET Core Global Tool ) que se puede instalar fácilmente desde NuGet . La documentación está escrita. Intenté mucho escribir en detalle y claramente y, al parecer, sucedió. ¡Puedes tomar y usar!
Un poco sobre el nombre ...
El nuevo migrador no tiene compatibilidad con el anterior. Trabajan en diferentes plataformas y tienen una API diferente. Por lo tanto, el proyecto se publica con un nombre diferente.
El nombre fue elegido por el proyecto ThinkingHome , para el cual porté un migrador. En realidad, ECM7.Migrator también lleva el nombre del proyecto en el que estaba trabajando en ese momento.
Quizás fue mejor elegir un nombre neutral, pero no se me ocurrió tener buenas opciones. Si sabes esto, escribe en los comentarios. No es demasiado tarde para cambiar el nombre de todo.
El número de versión indicaba 3.0.0, porque El nuevo migrador es una continuación lógica del antiguo.
Inicio rápido
Entonces, intentemos usar un migrador.
Todos los cambios en la base de datos se registran en el código de migración: clases escritas en un lenguaje de programación (por ejemplo, en C #). Las clases de migración heredan de la clase base Migration
del paquete ThinkingHome.Migrator.Framework . En ellos, debe anular los métodos de la clase base: Apply
(aplicar los cambios) y Revert
(revertir los cambios). Dentro de estos métodos, el desarrollador, utilizando una API especial, describe las acciones que deben realizarse en la base de datos.
Además, la clase de migración debe estar marcada con el atributo [Migration]
e indicar la versión a la que irá la base de datos después de realizar estos cambios.
Ejemplo de migración
using ThinkingHome.Migrator.Framework; [Migration(12)] public class MyTestMigration : Migration { public override void Apply() { // : Database.AddTable("CustomerAddress", new Column("customerId", DbType.Int32, ColumnProperty.PrimaryKey), new Column("addressId", DbType.Int32, ColumnProperty.PrimaryKey)); } public override void Revert() { // : Database.RemoveTable("CustomerAddress"); // , // Revert } }
Como correr
Las migraciones se compilan en un archivo .dll. Después de eso, puede realizar cambios en la base de datos utilizando la utilidad de consola de migrate-database
. Para comenzar, instálelo desde NuGet .
dotnet tool install -g thinkinghome.migrator.cli
Ejecute migrate-database
, especificando el tipo de DBMS requerido, la cadena de conexión y la ruta al archivo .dll con migraciones.
migrate-database postgres "host=localhost;port=5432;database=migrations;" /path/to/migrations.dll
Ejecutar a través de la API
Puede realizar migraciones a través de la API desde su propia aplicación. Por ejemplo, puede escribir una aplicación que, cuando se inicia, crea la estructura de base de datos deseada.
Para hacer esto, conecte el paquete ThinkingHome.Migrator de NuGet y el paquete con el proveedor de transformación para el DBMS requerido a su proyecto. Después de eso, cree una instancia de la clase ThinkingHome.Migrator.Migrator
y llame a su método Migrate
, pasando la versión requerida de la base de datos como parámetro.
var version = -1; // -1 var provider = "postgres"; var connectionString = "host=localhost;port=5432;database=migrations;"; var assembly = Assembly.LoadFrom("/path/to/migrations.dll"); using (var migrator = new Migrator(provider, connectionString, assembly)) { migrator.Migrate(version); }
Por cierto, puedes comparar con el ejemplo de lanzamiento de FluentMigrator.
Conclusión
Traté de hacer una herramienta simple sin adicciones y magia compleja. Parece haber funcionado bastante bien. El proyecto no ha sido crudo durante mucho tiempo, todo está cubierto en pruebas, hay documentación detallada en ruso. Si está utilizando .NET Core 2.1, pruebe un nuevo migrador . Lo más probable es que también te guste.