Entity Framework - Migrations - le Premier Code de Semis par la Migration
Je me penche sur les Migrations dans le but de nettoyer nos processus de déploiement. Le moins d'intervention manuelle nécessaire pour pousser un changement à la production le mieux.
J'ai rencontré 3 accrocs majeurs avec le système de migrations. Ils sont des bouchons si je ne peux pas trouver un moyen propre autour d'eux.
1. Comment ajouter des données de départ par migration:
J'exécute la commande "add-migration" qui échafaudage un nouveau fichier de migration avec des fonctions haut et bas. 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 de départ à la Configuration.Méthode Seed car cela fonctionne pour toutes les migrations qui se termine par toutes sortes de problèmes de duplication.
2. Si ce qui précède n'est pas possible, Comment puis-je é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 le code ci-dessus m'amène à mon 3ème et dernier problème:
3. Comment puis-je semer 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 qu'il soit 10,20,30?
Ces limitations de EF sont-elles pour le moment ou sont-elles des contraintes intentionnelles pour empêcher une autre sorte de catastrophe que je ne vois pas?
4 réponses
- Quand j'ai fixé des données 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 à mi-chemin de cette page: Comment insérer des données fixes - vous empêchez 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.
- les clés primaires qui sont des entiers sont créées en tant qu'identité les champs par défaut. Pour spécifier autrement, utilisez l'attribut
[DatabaseGenerated(DatabaseGeneratedOption.None)]
Je pense que c'est une bonne explication des méthodes initialiseur et Seed
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'article 1, j'ai fait une implémentation de la stratégie IDatabaseInitializer
qui exécutera la méthode Seed de chaque migration en attente seulement, vous devrez implémenter une interface IMigrationSeed
personnalisée dans chacune de vos classes DbMigration
, la méthode Seed
sera ensuite implémentée juste après les méthodes Up
et Down
de chaque classe de migration.
Cela aide à résoudre deux problèmes pour moi:
- migration de modèle de base de données de groupe avec Migration de données de base de données (ou Ensemencement)
- vérifiez quelle partie du code de migration des semences doit réellement être exécutée, ne vérifiant pas les données dans la base de données mais en utilisant des données déjà connues qui sont 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 pour manipuler les données de départ, tout comme l'implémentation de départ EF standard. Cependant cela peut obtenir étrange si, par exemple, vous décidez de supprimer une table qui a été ensemencée lors d'une migration précédente, vous devrez refactoriser votre code de départ existant en conséquence.
Modifier: Comme alternative à l'implémentation de la méthode seed après le haut et le bas, 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 quand je veux re-semer la même migration.
Salut j'ai trouvé une information très utile pour votre problème dans ce lien: Safari Livres En Ligne
"1. Comment ajouter des données de départ par migration: " Comme vous le voyez dans l'exemple, vous devez créer un nouveau confiugration pour l'ensemencement. Cette Configuration de départ 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 puis-je éviter les doublons?"
AddOrUpdate doit vous aider exactement à éviter les doublons si vous obtenez un erreur ici, vous pourriez avoir une erreur de configuration postez la pile d'appels svp. Voir l'exemple!
"3. Comment puis-je semer des clés primaires?"
Ici, il est également sur votre définition de clé. Si votre clé DatabaseGenerated(DatabaseGeneratedOption.Identity)
que vous n'avez pas à fournir. Dans d'autres senarios, vous devez en créer un nouveau en fonction du type de clé.
"est ce que ces limitations de EF pour le moment ou sont elles des contraintes intentionnelles pour empêcher un autre type de catastrophe Je ne le suis pas voir?"
Pas que je sache!
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 pour une migration spécifique. Tout doit aller dans la Configuration commune.Graines de méthode.
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, fondamentalement, il suffit de vérifier si elle existe et d'ajouter sinon
3. pour que ce qui précède fonctionne, vous devez pouvoir 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ément 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; }
}