2017-05-26 1 views
1

Maintenant que certains d'entre nous ont des projets Code First en production depuis des années et accumulent beaucoup de migrations, est-ce que quelqu'un en a rencontré un grand nombre? Y a-t-il une chose comme "trop ​​de migrations?"Lutte contre les ballonnements de migration dans Entity Framework Code Premières applications

Si oui, quel est le remède? Quelques mises en garde: - Évidemment, nous ne pouvons pas supprimer & rescaffold la production db. - Nous ne pouvons pas supprimer toutes les migrations, __MigrationHistory, et créer une nouvelle initiale (dans mon cas) car beaucoup de nos migrations ont des graines de données/mises à jour, et même des modifications aux commandes générées.

Existe-t-il un moyen/un outil pour combiner les migrations en moins de migrations? Cela ferait-il même une différence?

Merci!

+0

J'ai une base de données prod avec le code d'abord, nous avons supprimé toute la migration du dossier de migration dans le projet. – ISHIDA

+0

Je devrais ajouter que nous utilisons NCrunch pour exécuter des tests DB automatisés, donc ces tests nécessitent toutes les migrations –

+0

Je dirais créer une migration vide qui contient toutes vos migrations. Au lieu d'une migration trop importante, vous aurez une seule migration, ce qui réduira le temps de migration et augmentera la vitesse – ISHIDA

Répondre

0

À partir des suggestions de l'ISHIDA, j'ai créé un exemple d'une façon de combiner les migrations. Ce n'est en aucun cas la seule solution/correcte, et elle ne répond pas non plus à la question de savoir si la migration est un problème, mais c'est un bon début.

Pour tester cela, j'ai une application console avec 2 tables, qui sont:

public class Account 
{ 
    [Required] 
    [StringLength(100)] 
    public string Id { get; set; } 

    [Required] 
    [StringLength(10)] 
    public string AccountNumber { get; set; } 

    public virtual List<Policy> Policies { get; set; } 
} 

public class Policy 
{ 
    [Required] 
    [StringLength(100)] 
    public string Id { get; set; } 

    [Required] 
    public int PolicyNumber { get; set; } 

    [Required] 
    public string AccountId { get; set; } 
    public virtual Account Account { get; set; } 
} 

Il y a 4 migrations qui ont créé ces tables, ajoutées les données et ont changé le type de données PolicyNumber de chaîne int. Imaginez que ce programme est en ligne et que tous ont été exécutés dans un environnement de production.

public partial class InitialCreate : DbMigration 
{ 
    public override void Up() 
    { 
     CreateTable(
      "dbo.Accounts", 
      c => new 
       { 
        Id = c.String(nullable: false, maxLength: 100), 
        AccountNumber = c.String(nullable: false, maxLength: 10), 
       }) 
      .PrimaryKey(t => t.Id); 
    } 

    public override void Down() 
    { 
     DropTable("dbo.Accounts"); 
    } 
} 

public partial class SeedAccounts : DbMigration 
{ 
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" }; 

    public override void Up() 
    { 
     Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[0]}','101')"); 
     Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[1]}','102')"); 
    } 

    public override void Down() 
    { 
     Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[0]}'"); 
     Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[1]}'"); 
    } 
} 
} 

public partial class AddPolicyTable : DbMigration 
{ 
    public override void Up() 
    { 
     CreateTable(
      "dbo.Policies", 
      c => new 
       { 
        Id = c.String(nullable: false, maxLength: 100), 
        PolicyNumber = c.String(nullable: false, maxLength: 100), 
        AccountId = c.String(nullable: false, maxLength: 100), 
       }) 
      .PrimaryKey(t => t.Id) 
      .ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true) 
      .Index(t => t.AccountId); 

    } 

    public override void Down() 
    { 
     DropForeignKey("dbo.Policies", "AccountId", "dbo.Accounts"); 
     DropIndex("dbo.Policies", new[] { "AccountId" }); 
     DropTable("dbo.Policies"); 
    } 
} 

public partial class ChangeAndSeedPolicies : DbMigration 
{ 
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" }; 
    readonly string[] policyIds = new string[] { "IdPol101a", "IdPol101b", "IdPol102a" }; 

    public override void Up() 
    { 
     AlterColumn("dbo.Policies", "PolicyNumber", c => c.Int(nullable: false)); 

     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[0]}', '{accountIds[0]}', '10101')"); 
     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[1]}', '{accountIds[0]}', '10102')"); 
     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[2]}', '{accountIds[1]}', '10201')"); 

    } 

    public override void Down() 
    { 
     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[0]}'"); 
     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[1]}'"); 
     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[2]}'"); 

     AlterColumn("dbo.Policies", "PolicyNumber", c => c.String(nullable: false, maxLength: 100)); 
    } 
} 

Voici le code principal du projet:

 using (var dc = new DataContext()) 
     { 
      foreach (var account in dc.Accounts.OrderBy(q => q.AccountNumber).ToList()) 
      { 
       Console.WriteLine("Account " + account.AccountNumber); 

       foreach (var policy in account.Policies) 
        Console.WriteLine(" Policy " + policy.PolicyNumber); 
      } 
     } 

DataContext Classe:

public class DataContext : DbContext 
{ 
    public DataContext() : base("DefaultConnection") { } 

    public DbSet<Account> Accounts { get; set; } 
    public DbSet<Policy> Policies { get; set; } 
} 

La sortie est:

Account 101 
    Policy 10101 
    Policy 10102 
Account 102 
    Policy 10201 

Très simple. Maintenant, je veux combiner ces migrations en une seule. Rappelez-vous:

  • nous ne voulons pas laisser tomber & rescaffold parce que la production aurait des données autres que ce que les migrations ajoutées
  • les migrations antérieures doivent pouvoir être recommencée pour les tests d'intégration & nouveaux environnements

Ce sont les étapes que je suivais:

  • sauvegarder tout environnement que vous utiliserez.
  • créer une nouvelle migration (il doit être vide, car rien n'a encore changé)
  • dans le paquet Console Manager (PMC), exécutez « update-base de données » pour créer l'enregistrement de __MigrationHistory
  • Vérifier l'application fonctionne normalement à ce point
  • copie tout méthodes d'anciennes migrations vers le nouveau
  • copie toutes des méthodes d'anciennes migrations vers le nouveau dans l'ordre inverse
  • Vérifier l'application fonctionne normalement à ce stade (doit détecter aucun nouvelles migrations nécessaires)
  • supprimer toutes les anciennes migrations
  • supprimer tous les anciens enregistrements __MigrationHistory (ne laissant que la nouvelle)
  • Vérifier l'application fonctionne normalement à ce moment

Pour vérifier la nouvelle migration a vraiment tout ce que les anciens fait (pour de nouveaux environnements ou tests), supprimez simplement toutes les tables de la base de données (y compris __MigrationHistory), exécutez "update-database" dans PMC, et voyez si elle s'exécute.

C'est ce que ma nouvelle migration ressemble:

public partial class CombinedMigration : DbMigration 
{ 
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" }; 
    readonly string[] policyIds = new string[] { "IdPol101a", "IdPol101b", "IdPol102a" }; 

    public override void Up() 
    { 
     CreateTable(
      "dbo.Accounts", 
      c => new 
      { 
       Id = c.String(nullable: false, maxLength: 100), 
       AccountNumber = c.String(nullable: false, maxLength: 10), 
      }) 
      .PrimaryKey(t => t.Id); 

     Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[0]}','101')"); 
     Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[1]}','102')"); 

     CreateTable(
      "dbo.Policies", 
      c => new 
      { 
       Id = c.String(nullable: false, maxLength: 100), 
       PolicyNumber = c.String(nullable: false, maxLength: 100), 
       AccountId = c.String(nullable: false, maxLength: 100), 
      }) 
      .PrimaryKey(t => t.Id) 
      .ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true) 
      .Index(t => t.AccountId); 

     AlterColumn("dbo.Policies", "PolicyNumber", c => c.Int(nullable: false)); 

     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[0]}', '{accountIds[0]}', '10101')"); 
     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[1]}', '{accountIds[0]}', '10102')"); 
     Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[2]}', '{accountIds[1]}', '10201')"); 
    } 

    public override void Down() 
    { 
     // Each prior "Down" section was added in reverse order. 

     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[0]}'"); 
     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[1]}'"); 
     Sql($"DELETE FROM Policies WHERE ID = '{policyIds[2]}'"); 

     AlterColumn("dbo.Policies", "PolicyNumber", c => c.String(nullable: false, maxLength: 100)); 

     DropForeignKey("dbo.Policies", "AccountId", "dbo.Accounts"); 
     DropIndex("dbo.Policies", new[] { "AccountId" }); 
     DropTable("dbo.Policies"); 

     Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[0]}'"); 
     Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[1]}'"); 

     DropTable("dbo.Accounts"); 
    } 
} 

caveat: Si l'un de vos migrations ont un code .NET qui crée un nouveau contrôleur de domaine et fait quelques mises à jour de db, ceux-ci peuvent ne pas fonctionner lorsque les migrations sont combinées . Par exemple, si la migration 1 ajoute la table Compte et que la migration 2 utilise le code .NET pour insérer des enregistrements dans le compte, elle se bloquera dans la migration combinée, car le compte n'a pas encore été créé techniquement. Le remplacement de ces instructions par des instructions Sql ('INSERT INTO ...) corrigera ceci:

+0

Que gagnez-vous? Un plus petit nombre de fichiers de migration pour un fichier géant avec beaucoup de potentiel d'erreurs. Vous pouvez toujours supprimer les migrations au premier plan si vous savez que toutes les bases de données déployées ont été appliquées. Donc, si vous avez 100 migrations, mais que vous savez que 1 à 50 ont été déployées, vous pouvez les supprimer. Si vous vous inquiétez de l'apparition d'une ancienne base de données, lancez le script et stash avant la suppression. –

+0

Oui, je gagne un plus petit nombre de migrations, si le nombre de migrations devenait un problème. En outre, le message explique pourquoi j'ai besoin de toutes les migrations originales. –