À 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:
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
Je devrais ajouter que nous utilisons NCrunch pour exécuter des tests DB automatisés, donc ces tests nécessitent toutes les migrations –
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