web-dev-qa-db-fra.com

Comment gérer les migrations dans un projet à plusieurs branches?

J'ai un projet ASP.NET MVC3 qui utilise Entity Framework 4.3 avec l'approche code-first. J'utilise Migrations pour maintenir la base de données à jour.

Le projet est sous contrôle de source et j'ai plusieurs succursales. Ce que je viens de réaliser, c'est qu'il y aura un problème lorsque je veux fusionner une de mes branches dans le maître. Puisque j'ai créé des fichiers de migration dans les deux branches, il y aura des migrations qui se chevauchent lorsque je fusionne, ce qui causera probablement des conflits.

Existe-t-il un bon moyen de gérer les migrations dans un projet avec plusieurs branches?

Mise à jour

Une façon serait de fusionner, puis de supprimer tous les fichiers de migration créés alors que les branches étaient séparées, puis de créer un nouveau fichier de migration qui contient toutes les modifications depuis la création de la branche jusqu'à sa fusion. Cela fonctionnerait pour le environnement de développement où vous pouvez vider la base de données et la reconstruire avec tous les fichiers de migration. Le problème serait alors l'environnement vivant. Comme vous ne pouvez pas revenir à la date de création de la branche sans risque de perdre des données, il y aura un conflit lorsque vous essayez d'utiliser votre nouveau fichier de migration pour mettre à jour la base de données en direct.

53

Je pense que la réponse acceptée est incorrecte. Il existe une bien meilleure solution pour gérer les conflits de fusion de migration de framework d'entité sur une question similaire.

Tout ce que vous devez faire après une fusion est de rééchafauder les métadonnées de la migration dans la branche cible. C'est-à-dire que vous ne rescafoldez pas le code haut/bas, juste l'état dans le fichier resx.

add-migration [the_migration_to_rescaffold_metadata_for]

Cette procédure échouera cependant si une migration différente dans la fusion a modifié la base de données de telle sorte que la migration n'est plus exécutable ou donne un résultat inattendu. Cela étant dit - je pense que c'est un cas très rare car la plupart des migrations doivent être générées automatiquement, ou du moins ne pas dépendre d'autres tables qui ne sont pas également modifiées dans la migration elle-même. Problème très mineur mais dont il faut être conscient.

Un tel cas pourrait être fxp (je ne pouvais pas penser à un meilleur exemple)

  • La colonne foo est un entier et les lignes contiennent [0, 1, 2]

  • Migration A de la branche A changer foo en booléen (0 deviendra automatiquement faux et> 0 deviendra vrai)

  • La migration B de la branche B change foo en chaîne. Il s'attend à ce que ce soit un int mais c'est un booléen, la migration réussira cependant. Les données seront perdues car lors de la création de la migration B, les lignes contiendraient ["0", "1", "2"]. Lorsque la migration A a modifié la colonne en booléen (et l'a fait avec succès et avec le résultat attendu), les lignes contiendront désormais ["0", "1", "1"] à la place et la migration B aura un résultat final différent de celui observé dans Branche B.

Il y a probablement plus de cas Edge où les choses pourraient mal tourner avec la solution. Mais si le code ascendant/descendant des migrations ne dépend pas des choses modifiées par une autre migration dans la fusion, cela devrait bien fonctionner pour simplement mettre à jour les métadonnées dans les migrations.

17
oldwizard

Edit: un de mes collègues a découvert un moyen plus facile de le faire, j'ai laissé ma réponse originale en bas pour être complet.

(TRÈS IMPORTANT) les migrations dans un environnement réel ne doivent pas entrer en conflit avec celles de votre branche actuelle, sinon vous devez refaire toutes vos migrations et résoudre les conflits de changement de modèle de données à la main.

  1. restaurer votre base de données de développement avec des données d'environnement en direct
  2. courir update-database, il devrait exécuter des migrations à partir de votre branche et se plaindre de "impossible de mettre à jour la base de données pour correspondre au modèle actuel
  3. courir add-migration MergeBranchBToMaster -ignoreChanges, cela créera une migration vide.
  4. courir update-database encore
  5. Poussez vos changements

La magie de l'étape 3 indique fondamentalement à EF de se taire sur les modèles incompatibles, soyez donc très sûr que vos migrations ne sont pas en conflit avec celles en environnement réel. S'ils le font, vous pouvez toujours créer des scripts SQL pour pousser les migrations manquantes (qui est en fait la méthode préférée).

Réponse originale

J'ai trouvé une solution assez simple basée sur la réponse de @Ladislav Mrnka. Cela fonctionnera avec un environnement en direct [1], il vous suffit de faire attention à ne pas modifier les migrations déployées.

  1. Avant la fusion, notez la migration que vous avez ajoutée (MyMigration) et sa migration précédente (BaseMigration)

  2. Fusionner les branches dans git

  3. Ouvrez la console du gestionnaire de packages et exécutez: UPDATE-DATABASE -TargetMigration: BaseMigration. Cela ramènera votre base de données à l'état avant l'application des migrations en conflit

  4. Supprimer votre migration locale (MyMigration)

  5. Exécutez: UPDATE-DATABASE. Cela appliquera toutes les migrations plus récentes effectuées dans d'autres branches.

  6. Exécutez: ADD-MIGRATION MyMigration. Cela va générer à nouveau votre migration locale en fonction de l'état actuel de la base de données, comme git -rebase.

  7. Exécutez: UPDATE-DATABASE. Mettez à jour la base de données avec votre migration locale.

Cela fonctionne également si vous avez plusieurs migrations locales, mais il les fusionnera toutes en une seule.

[1] en travaillant avec un environnement en direct, je veux dire que la migration générée peut être appliquée à un environnement en direct qui peut déjà avoir certaines/toutes les migrations des autres branches appliquées. Les étapes elles-mêmes sont purement à des fins de développement.

15
Bill Yang

La fusion des migrations est une tâche manuelle à mon humble avis. Une partie du code de migration est générée automatiquement et nous ne fusionnons généralement pas le code généré automatiquement - à la place, nous réexécutons la génération automatique après la fusion.

Jusqu'à ce que l'équipe ADO.NET fournisse une recommandation, je suivrais un principe simple:

  • Avant de procéder à la fusion, rétablissez la base de données principale dans la version utilisée avant la création de branches
  • Fusionnez vos succursales
  • Exclure les classes de migration créées après la ramification de l'assembly fusionné
  • Ajoutez une nouvelle migration pour la base de code fusionnée qui fera migrer votre base de données dans l'état avant de créer une branche vers l'état après la fusion des branches
  • Si vos classes de migration exclues contiennent une certaine personnalisation, fusionnez-les avec la nouvelle classe de migration
  • Exécutez la migration pour migrer votre base de données vers la version fusionnée actuelle

Si vos branches contiennent plusieurs étapes de migration (version), vous les perdrez et vous terminerez avec deux versions - avant la branche et après la fusion.

Éditer:

Cela ne fonctionnera pas dans un environnement vivant. Le problème ici serait le processus de développement lui-même. Si vous avez un environnement en direct, vous devez garder sa branche intacte (sauf les corrections de bugs mineurs). Si vous continuez le développement dans cette branche avec un déploiement de production et en même temps que vous construisez une autre version dans une branche distincte sans intégration continue (= la fusion continue revient à la branche principale pour intégrer votre nouveau développement avec la base de code principale), vous avez un gros problème. Je pense que les migrations en général ne peuvent pas gérer cela.

Dans ce cas, la seule option serait probablement de supprimer toutes les migrations de la solution fusionnée et de supprimer la table MigrationHistory de la base de données. Vous pouvez ensuite réactiver les migrations sur le projet et ajouter la migration initiale pour utiliser votre base de données actuelle comme point de départ = aucun moyen de revenir à la version précédente car aucune information sur les migrations précédentes n'existera.

13
Ladislav Mrnka

Rowan Miller a réalisé une superbe vidéo sur ce sujet sur le canal 9: Migrations - Team Environments . Il fait référence au cadre d'entité 6.

Il décrit un scénario dans lequel les premiers développeurs A et B travaillent sur le même modèle et A s'enregistre en premier. Maintenant, le développeur B doit faire face aux problèmes qu'il a lorsqu'il obtient la dernière version de A.

C'est essentiellement la même chose que d'avoir des conflits entre différentes branches, car le problème général est que la fusion des modifications de migration a été effectuée en même temps mais avec un état source différent du modèle.

La solution est:

  • Lors de la résolution des conflits du système de contrôle de version, le développeur B doit accepter les modifications de lui-même et du développeur A.
  • Une commande UpdateDatabase du développeur B échouerait toujours à ce moment (Message d'erreur: "Impossible de mettre à jour la base de données pour correspondre au modèle actuel car des modifications sont en attente ...")
  • Le développeur B doit créer une "migration vide" en utilisant l'option IgnoreChanges:

Add-Migration NameOfMigration -IgnoreChanges

Ensuite, la commande UpdateDatabase réussira.


Source du problème

La source de l'erreur qui se produit lors de la mise à jour de la base de données est que EF stocke un instantané du modèle auquel une migration fait référence dans le fichier resx dans le fichier de migration.

Dans ce cas, l'instantané B du développeur du "modèle actuel" n'est pas correct après avoir obtenu/fusionné les modifications apportées par le développeur A.

9
Martin

J'y ai réfléchi et j'espère contribuer aux différentes opinions et pratiques présentées ici.

Considérez ce que vos migrations locales représentent réellement. Lorsque je travaille localement avec une base de données de développement, j'utilise des migrations pour mettre à jour la base de données de la manière la plus pratique possible lors de l'ajout de colonnes, etc. aux tables, de l'ajout de nouvelles entités, etc.

Ainsi, Add-Migration vérifie mon modèle actuel (appelons-le modèle b) par rapport à mon modèle précédent (modèle a) et génère une migration pour aller de a => b dans la base de données.

Pour moi, cela n'a pas beaucoup de sens d'essayer de fusionner mes migrations avec n'importe qui d'autre migrations, si tout le monde a en effet sa propre base de données et il existe alors une sorte d'étape/test/serveurs de base de données dev/production dans l'organisation. Tout dépend de la façon dont l'équipe l'a mise en place, mais il est logique de s'isoler mutuellement des changements que les autres effectuent si vous voulez vraiment travailler de manière distribuée.

Eh bien, si vous travaillez distribué et avez une entité, Personne, par exemple, sur laquelle vous travaillez. Pour une raison quelconque, beaucoup d'autres personnes y travaillent également. Donc, vous ajoutez et supprimez des propriétés sur Person selon les besoins de votre histoire particulière dans le sprint (nous travaillons tous agiles ici, n'est-ce pas?), Comme le numéro de sécurité sociale que vous avez d'abord transformé en entier parce que vous n'êtes pas que lumineux, puis à une chaîne, etc.

Vous ajoutez FirstName et LastName.

Vous avez alors terminé et vous avez dix migrations de haut en bas étranges (vous en avez probablement supprimé certaines en travaillant car elles étaient juste de la merde) et vous récupérez quelques modifications dans le dépôt central de Git. Sensationnel. Votre collègue Bob avait également besoin de quelques noms, peut-être auriez-vous dû vous parler?

Quoi qu'il en soit, il a ajouté NameFirst et NameLast, je suppose ... alors que faites-vous? Eh bien, vous fusionnez, refactorisez, changez pour qu'il ait plus de noms sains d'esprit ... comme FirstName et LastName, vous exécutez vos tests et vérifiez son code, puis vous poussez vers la centrale.

Mais qu'en est-il des migrations? Eh bien, il serait maintenant temps de faire une migration en déplaçant le référentiel central, ou la branche "test" plus spécifiquement, contenir une jolie petite migration de son modèle a => modèle b. Cette migration sera une et une seule migration, pas dix étranges.

Voyez-vous où je veux en venir? Nous travaillons avec de jolis petits pocos et leurs comparaisons constituent les migrations réelles. Donc, nous ne devrions pas du tout fusionner les migrations, à mon avis, nous devrions avoir des migrations par branche ou quelque chose comme ça.

En fait, devons-nous même créer la migration dans la branche après la fusion? Oui, si cette base de données est mise à jour automatiquement, nous devons le faire.

Je dois travailler un peu plus, ce sont mes pensées à ce sujet, au moins.

4
LavaEater

Pensez à utiliser une bibliothèque de migration différente qui ne provoque pas ces conflits, telle que FluentMigrator ou Migrator.NET.

Je ne pense pas que les migrations EF soient vraiment prêtes pour une utilisation générale avec les branches et les fusions - c'est beaucoup de travail et trop facile de faire des erreurs désagréables.

2
Eamon Nerbonne

Je pense que ce que dit @LavaEater est très logique. J'implémente une stratégie de branchement (développement, principal, version) et je l'aligne avec les environnements dans le processus de développement, d'assurance qualité et de version.

  • Branche Développement - Développement local
  • Branche principale - Fusionner les modifications de la branche Développement et déployer sur mon environnement de transfert (site Web Azure et base de données SQL)
  • Branche des versions - Fusionner les modifications de Main et déployer vers l'environnement de production (un autre site Web Azure et une base de données SQL)

Je me suis heurté au problème discuté ci-dessus et à mon avis, les complications liées aux migrations et les solutions de contournement potentielles introduisent beaucoup de risques dans le processus de publication. L'exécution de migrations indépendantes dans Development, Main et Release signifie que le schéma que j'ai inclus dans la génération dans Dev n'est pas le schéma qui va dans QA on Staging et le schéma que QA signe sur Staging n'est pas le schéma qui est déployé sur Live ( à moins que je ne suive l'une des solutions suggérées qui, j'en suis sûr, fonctionneraient mais pourraient être sujettes aux erreurs).

Pour faire écho à @LavaEater - quel est le véritable avantage que j'obtiens en premier du code EF? Personnellement, je pense que c'est la facilité avec laquelle je peux générer un schéma à partir du code (et éventuellement modifier les migrations générées automatiquement si je le souhaite). Après cela, les migrations sont une complication de ce qui devrait être un simple processus de déploiement.

Ma pensée actuelle est d'utiliser d'abord du code pour générer les migrations en développement et ensuite: -

  • Option A) - Utilisez Update-Database -script pour écrire les modifications de schéma et les placer sous contrôle de source. Il y a toujours un potentiel de conflits si 2 personnes modifient le même modèle mais je pense que c'est plus facile à gérer.

  • Option B) - Utilisez quelque chose comme SQL Compare pour générer des scripts de changement de schéma. C'est potentiellement plus flexible et transparent car j'aime voir exactement quels changements de schéma j'applique à ma base de données de production (appelez-moi paranoïaque).

Suis-je en train de manquer quelque chose? J'imagine qu'il y aura une configuration à faire pour désactiver les premières migrations de code dans les branches Main et Release (en supposant que la base de données sera créée et mise à jour par des scripts). A part ça, cela ressemble à une solution sûre mais j'apprécierais un deuxième avis.

0
Liam Weston