Salut Aujourd'hui, j'ai publié une nouvelle version de ThinkingHome.Migrator - un outil pour la migration versionnée d'un schéma de base de données vers la plate-forme .NET Core.

Packages publiés dans NuGet , documentation détaillée écrite. Vous pouvez déjà utiliser le nouveau migrateur, et je vais vous dire comment il est apparu, pourquoi il a la version 3.0.0 (bien que ce soit la première version) et pourquoi il est nécessaire quand il y a EF Migrations et FluentMigrator .
Comment tout a commencé
Il y a 9 ans, en 2009, j'ai travaillé en tant que développeur ASP.NET. Lorsque nous avons publié notre projet, une personne spéciale est restée debout tard et, en même temps, mettant à jour les fichiers sur le serveur, a exécuté des scripts SQL avec ses mains pour mettre à jour les bases de données dans le prod. Nous avons recherché un outil qui le ferait automatiquement et avons trouvé le projet Migrator.NET .
Le migrant a proposé une nouvelle idée pour l'époque: définir les modifications de la base de données sous la forme de migrations. Chaque migration contient une petite partie des modifications et possède un numéro de version dans lequel la base de données ira une fois terminée. Le migrant lui-même a gardé une trace des versions et a effectué les migrations nécessaires dans l'ordre nécessaire. Le fait que le migrateur ait permis de définir des changements inverses pour chaque migration était particulièrement cool. Il était possible de définir la version en dessous de la version actuelle lorsque le migrant a démarré, et il ramènerait automatiquement la base de données vers cette version, effectuant les migrations nécessaires dans l'ordre inverse.
[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"); } }
Il y avait beaucoup d'erreurs dans ce migrant. Il ne savait pas comment travailler avec des schémas de base de données autres que le schéma par défaut. Dans certains cas, il a généré des requêtes SQL incorrectes et si vous spécifiez un numéro de version inexistant, il tombe dans une boucle infinie. En conséquence, mes collègues et moi avons bifurqué le projet et corrigé des bugs là-bas.
GitHub.com avec ses fourchettes et demandes de pull n'était pas là alors (le code du migrant était sur code.google.com ). Par conséquent, nous n'avons surtout pas pris la peine de nous assurer que nos modifications reviennent dans le projet d'origine - nous venons de scier notre copie et de l'utiliser nous-mêmes. Au fil du temps, nous avons réécrit la majeure partie du projet et je suis devenu son principal responsable. J'ai ensuite posté le code de notre migrateur sur google code et écrit un article sur Habr . Il y avait donc ECM7.Migrator.
Lors des travaux sur le migrateur, nous l'avons presque entièrement réécrit. En même temps, ils ont un peu simplifié l'API et couvert tout avec des tests automatiques. Personnellement, j'ai vraiment apprécié d'utiliser ce qui s'est passé. Contrairement au migrateur d'origine, il y avait un sentiment de fiabilité et il n'y avait aucun sentiment qu'une magie étrange se produisait.
Il s'est avéré que non seulement j'aimais notre migrateur. Pour autant que je sache, il a été utilisé dans des entreprises assez importantes. Je connais ABBYY, BARS Group et concert.ru. Si vous tapez dans la requête de recherche "migrateur ecm7", vous pouvez voir dans les résultats des articles à ce sujet, des mentions dans le CV, des descriptions d'utilisation dans le travail étudiant. Parfois, je recevais des lettres d'étrangers avec des questions ou des mots de gratitude.
Après 2012, le projet ne s'est presque pas développé. Ses capacités actuelles couvraient toutes les tâches que j'avais et je ne voyais pas la nécessité de terminer quelque chose.
ThinkingHome.Migrator
L'année dernière, j'ai commencé à travailler sur un projet sur .NET Core. Là, il était nécessaire de permettre la connexion des plugins, et les plugins devraient pouvoir créer la structure de base de données nécessaire pour y stocker leurs données. C'est exactement une telle tâche, pour laquelle un migrant est bien adapté.
Migrations EF
J'ai utilisé Entity Framework Core pour travailler avec la base de données, donc la première chose que j'ai essayée a été EF Migrations. Malheureusement, j'ai dû abandonner presque immédiatement l'idée de les utiliser.
Les migrations Entity Framework font glisser un tas de dépendances dans le projet et au démarrage, elles font de la magie spéciale. Un pas à gauche / un pas à droite - vous rencontrez des restrictions. Par exemple, les migrations de Entity Framework pour une raison quelconque doivent nécessairement se faire dans le même assembly avec DbContext
. Cela signifie qu'il ne sera pas possible de stocker les migrations à l'intérieur des plugins.
Fluentmigrator
Quand il est devenu clair que EF Migrations ne convenait pas, j'ai cherché une solution dans google et trouvé plusieurs migrateurs open source. Le plus avancé d'entre eux, à en juger par le nombre de téléchargements sur NuGet et d'étoiles sur GitHub, s'est avéré être FluentMigrator . La FM est très bonne! Il en sait beaucoup et possède une API très pratique. Au début, j'ai décidé que c'était ce dont j'avais besoin, mais plus tard, plusieurs problèmes ont été découverts.
Le principal problème est que FluentMigrator ne sait pas prendre en compte plusieurs séquences de versions au sein d'une même base de données. Comme je l'ai écrit ci-dessus, j'avais besoin d'utiliser un migrateur dans une application modulaire. Il est nécessaire que les modules (plugins) puissent être installés et mis à jour indépendamment les uns des autres. FluentMigrator possède une numérotation de version de bout en bout. Pour cette raison, il est impossible d'exécuter / d'annuler la migration d'un plugin de la base de données sans affecter la structure de la base de données des plugins restants.
J'ai essayé d'organiser le comportement souhaité à l'aide de balises , mais ce n'est également pas tout à fait ce dont vous avez besoin. FluentMigrator ne stocke pas les informations de balise pour les migrations terminées. De plus, les balises sont liées aux migrations, pas aux assemblys. C'est très étrange, étant donné que le point d'entrée pour la migration est exactement l'assemblage. En principe, il était probablement possible de faire des versions parallèles de cette manière, mais vous devez écrire un wrapper assez compliqué sur le migrateur.
Portez ECM7.Migrator vers .NET Core
Au début, cette option n'était même pas envisagée. À cette époque, la version actuelle de .NET Core était 1.1 et son API était mal compatible avec le .NET Framework, dans lequel ECM7.Migrator fonctionnait. J'étais sûr que le porter sur .NET Core serait difficile et prendrait du temps. Quand il n'y avait pas d'options pour «prendre le fini», j'ai décidé de l'essayer. La tâche a été plus facile que ce à quoi je m'attendais. Étonnamment, cela a fonctionné presque immédiatement. Seules des modifications mineures ont été requises. La logique du migrateur ayant été couverte par des tests, tous les endroits qui se sont cassés étaient immédiatement visibles et je les ai rapidement réparés.
Maintenant, j'ai porté des adaptateurs pour seulement quatre SGBD: MS SQL Server, PostgreSQL, MySQL, SQLite. Je n'ai pas porté d'adaptateurs pour Oracle (car il n'y a toujours pas de client stable pour .NET Core), MS SQL Server CE (car il ne fonctionne que sous Windows et je n'ai pas de place pour l'exécuter) et Firebird (car il pas très populaire, port plus tard). En principe, si vous devez créer des fournisseurs pour ces SGBD ou d'autres - c'est assez simple.
Le code source du nouveau migrateur est sur GitHub . Configuration du lancement des tests pour chaque SGBD dans Travis CI. Un utilitaire de ligne de commande ( .NET Core Global Tool ) a été écrit et peut être facilement installé à partir de NuGet . La documentation est écrite - j'ai essayé très fort d'écrire en détail et clairement et, semble-t-il, cela s'est produit. Vous pouvez prendre et utiliser!
Un peu sur le nom ...
Le nouveau migrateur n'a pas de compatibilité descendante avec l'ancien. Ils travaillent sur différentes plates-formes et ils ont une API différente. Par conséquent, le projet est publié sous un nom différent.
Le nom a été choisi par le projet ThinkingHome , pour lequel j'ai porté un migrateur. En fait, ECM7.Migrator est également nommé pour le projet sur lequel je travaillais à ce moment-là.
Peut-être valait-il mieux choisir un nom neutre, mais je ne pensais pas avoir de bonnes options. Si vous le savez - écrivez dans les commentaires. Il n'est pas trop tard pour tout renommer.
Le numéro de version indiqué 3.0.0, car le nouveau migrateur est une continuation logique de l'ancien.
Démarrage rapide
Essayons donc d'utiliser un migrateur.
Toutes les modifications de la base de données sont enregistrées dans le code de migration - classes écrites dans un langage de programmation (par exemple, en C #). Les classes de migration héritent de la classe de base Migration
du package ThinkingHome.Migrator.Framework . Dans ceux-ci, vous devez remplacer les méthodes de la classe de base: Apply
(appliquer les modifications) et Revert
(annuler les modifications). À l'intérieur de ces méthodes, le développeur, à l'aide d'une API spéciale, décrit les actions qui doivent être effectuées sur la base de données.
De plus, la classe de migration doit être marquée avec l'attribut [Migration]
et indiquer la version dans laquelle la base de données ira après ces modifications.
Exemple de migration
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 } }
Comment courir
Les migrations sont compilées dans un fichier .dll. Après cela, vous pouvez effectuer des modifications de base de données à l'aide de l'utilitaire de console migrate-database
. Pour commencer, installez-le à partir de NuGet .
dotnet tool install -g thinkinghome.migrator.cli
Exécutez migrate-database
, en spécifiant le type de SGBD requis, la chaîne de connexion et le chemin d'accès au fichier .dll avec les migrations.
migrate-database postgres "host=localhost;port=5432;database=migrations;" /path/to/migrations.dll
Parcourez l'API
Vous pouvez effectuer des migrations via l'API à partir de votre propre application. Par exemple, vous pouvez écrire une application qui, lorsqu'elle est lancée, crée elle-même la structure de base de données souhaitée.
Pour ce faire, connectez le package ThinkingHome.Migrator de NuGet et le package avec le fournisseur de transformation pour le SGBD requis à votre projet. Après cela, créez une instance de la classe ThinkingHome.Migrator.Migrator
et appelez sa méthode Migrate
, en passant la version requise de la base de données en tant que paramètre.
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); }
À propos, vous pouvez comparer avec l' exemple de lancement FluentMigrator.
Conclusion
J'ai essayé de créer un outil simple sans dépendances et sans magie complexe. Cela semble avoir très bien fonctionné. Le projet n'a pas été grossier depuis longtemps, tout est couvert de tests, il y a une documentation détaillée en russe. Si vous utilisez .NET Core 2.1, essayez un nouveau migrateur . Vous l'aimerez probablement aussi.