J'examine les migrations dans le but de nettoyer nos processus de déploiement. Moins une intervention manuelle est nécessaire lors d'un changement de production, mieux c'est.
J'ai rencontré 3 problèmes majeurs avec le système de migration. Ce sont des bouchons si je ne peux pas trouver un moyen propre de les contourner.
1. Comment ajouter Seed données par migration:
J'exécute la commande "add-migration" qui échafaude un nouveau fichier de migration avec les fonctions Up et Down. Maintenant, je veux apporter automatiquement des modifications aux données avec des modifications à la fois vers le haut et vers le bas. Je ne veux pas ajouter les données Seed à la méthode Configuration.Seed car cela s'exécute pour toutes les migrations, ce qui se termine par toutes sortes de problèmes de duplication.
2. Si ce qui précède n'est pas possible, comment éviter les doublons?
J'ai une énumération que je boucle pour ajouter les valeurs à la base de données.
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
context.SaveChanges();
Même si j'utilise AddOrUpdate, je reçois toujours des doublons dans la base de données. Le code ci-dessus m'amène à mon troisième et dernier problème:
. Comment puis-je amorcer des clés primaires?
Mon énumérable avec le code ci-dessus est:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
public int AccessId { get; set; }
public string Name { get; set; }
}
Je spécifie les valeurs que je veux comme clé primaire, mais Entity Framework semble l'ignorer. Ils finissent toujours par être 1,2,3. Comment puis-je obtenir 10,20,30?
S'agit-il de limitations d'EF en ce moment ou s'agit-il de contraintes intentionnelles pour empêcher une autre sorte de catastrophe que je ne vois pas?
Sql("Insert ...")
. Voir la note au milieu de cette page: comment insérer des données fixes[DatabaseGenerated(DatabaseGeneratedOption.None)]
Je pense que c'est une bonne explication de Initializer et Seed methods
Voici un exemple d'utilisation de la méthode AddOrUpdate:
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
x => x.Name, //the natural key is "Name"
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
Comme solution possible à l'élément 1, j'ai fait une implémentation de la stratégie IDatabaseInitializer
qui exécutera la méthode Seed de chaque migration en attente uniquement, vous devrez implémenter une personnalisation IMigrationSeed
interface dans chacune de vos classes DbMigration
, la méthode Seed
sera alors implémentée juste après Up
et Down
méthodes de chaque classe de migration.
Cela aide à résoudre deux problèmes pour moi:
L'interface ressemble à ceci
public interface IMigrationSeed<TContext>
{
void Seed(TContext context);
}
Voici la nouvelle implémentation qui appellera cette méthode Seed
public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
: IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
public virtual void InitializeDatabase(TContext context)
{
var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
var pendingMigrations = migratorBase.GetPendingMigrations().ToArray();
if (pendingMigrations.Any()) // Is there anything to migrate?
{
// Applying all migrations
migratorBase.Update();
// Here all migrations are applied
foreach (var pendingMigration in pendingMigrations)
{
var migrationName = pendingMigration.Substring(pendingMigration.IndexOf('_') + 1);
var t = typeof(TMigrationsConfiguration).Assembly.GetType(
typeof(TMigrationsConfiguration).Namespace + "." + migrationName);
if (t != null
&& t.GetInterfaces().Any(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(IMigrationSeed<>)))
{
// Apply migration seed
var seedMigration = (IMigrationSeed<TContext>)Activator.CreateInstance(t);
seedMigration.Seed(context);
context.SaveChanges();
}
}
}
}
}
La bonne chose ici est que vous avez un vrai contexte EF à manipuler Seed Data, tout comme l'implémentation EF standard Seed. Cependant, cela peut devenir étrange si, par exemple, vous décidez de supprimer une table qui a été prédéfinie lors d'une migration précédente, vous devrez refactoriser votre code Seed en conséquence.
EDIT: Comme alternative pour implémenter la méthode de départ après Up et Down, vous pouvez créer une classe partielle de la même classe de migration, j'ai trouvé cela utile car cela me permet de supprimer en toute sécurité la classe de migration lorsque je veux réamorcer le même migration.
OK, donc avec un peu de dénigrement, j'ai réussi à bash EF dans la soumission. Voici ce que j'ai fait:
1. Il n'y a aucun moyen que j'ai trouvé pour voir les données d'une migration spécifique. Tout cela doit aller dans la méthode commune Configuration.Seed.
2. Pour éviter les doublons, j'ai dû faire 2 choses. Pour mes énumérations, j'ai écrit le code de départ suivant:
foreach (var enumValue in Enum.GetValues(typeof(Access.Level)))
{
var id = (int)enumValue;
var val = enumValue.ToString();
if(!context.Access.Any(e => e.AccessId == id))
context.Access.Add(
new Access { AccessId = id, Name = val }
);
}
context.SaveChanges();
Donc, en gros, il suffit de vérifier s'il existe et d'ajouter sinon
. Pour que ce qui précède fonctionne, vous devez être en mesure d'insérer des valeurs de clé primaire. Heureusement pour moi, cette table aura toujours les mêmes données statiques afin que je puisse désactiver l'incrémentation automatique. Pour ce faire, le code ressemble à:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccessId { get; set; }
public string Name { get; set; }
}
Salut, j'ai trouvé une information très utile pour votre problème dans ce lien: Safari Books Online
"1. Comment ajouter Seed données par migration:" Comme vous le voyez dans l'exemple, vous devez créer une nouvelle configuration pour l'amorçage. Cette configuration d'amorçage doit être appelée après la migration.
public sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SafariCodeFirst.SeminarContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
"2. Si ce qui précède n'est pas possible, comment éviter les doublons?"
AddOrUpdate Doit vous aider exactement à éviter les doublons si vous obtenez une erreur ici, vous pourriez avoir une erreur de configuration après la pile d'appels s'il vous plaît. Voir l'exemple!
"3. Comment puis-je amorcer des clés primaires?"
Ici, c'est aussi sur votre définition clé. Si votre clé DatabaseGenerated(DatabaseGeneratedOption.Identity)
que vous n'avez pas à la fournir. Dans certains autres scénarios, vous devez en créer un nouveau, selon le type de clé.
"Ces limitations d'EF sont-elles pour le moment ou sont-elles des contraintes intentionnelles pour empêcher un autre type de catastrophe que je ne vois pas?"
Pas que je sache!