web-dev-qa-db-fra.com

Entity Framework - Migrations - Code First - Amorçage par migration

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?

40
Talon
  1. Lorsque j'ai des données fixes que je veux insérer avec une migration, je mets les insertions directement dans la migration Up () en utilisant des appels à Sql("Insert ..."). Voir la note au milieu de cette page: comment insérer des données fixes
  2. Vous évitez les doublons dans la méthode Seed en appelant la surcharge AddOrUpdate qui prend une expression d'identifiant spécifiant la clé naturelle - voir cette réponse et cette entrée de blog .
  3. Les clés primaires qui sont des entiers sont créées par défaut comme champs d'identité. Pour spécifier le contraire, utilisez l'attribut [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() }
    );
}
28
Colin

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:

  1. Migration de modèle de base de données de groupe avec migration de données de base de données (ou amorçage)
  2. Vérifiez quelle partie du code de migration Seed devrait vraiment être en cours d'exécution, ne vérifiant pas les données dans la base de données mais utilisant des données déjà connues qui est le modèle de base de données qui vient d'être créé.

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.

12

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; }
}
3
Talon

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!

3
Bassam Alugili