web-dev-qa-db-fra.com

Comment semer dans Entity Framework Core 2?

J'ai deux tables et je veux le remplir avec des graines.

J'utilise ASP.NET Core 2 dans Ubuntu.

Comment renseigner les données de deux tables dont l'une est connectée à une autre par clé étrangère? Le débitmètre a beaucoup de notes et la note appartient au flomètre. Je veux faire quelque chose comme ça, mais cela devrait être stocké dans une base de données.

new Flowmeter {Make="Simple model name",SerialNum=45, Model="Lor Avon", Notes = new List<Note>()
    {
        new Note(){Value=45,CheckedAt=System.DateTime.Now},
        new Note(){Value=98,CheckedAt=System.DateTime.Now}
    }
}
35
zshanabek

À partir de Entity Framework Core 2.1 , une nouvelle méthode d’ensemencement des données est désormais disponible. Dans votre DbContext annulation de classe OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });
}

Et pour les entités liées, utilisez des classes anonymes et spécifiez la clé étrangère de l'entité associée:

modelBuilder.Entity<Post>().HasData(
    new {BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1"},
    new {BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2"});

Important: Veuillez noter que vous devrez exécuter une migration complémentaire après avoir entré ces données dans votre méthode OnModelCreating et dans Update-Database pour mettre à jour vos données.

Les documents officiels ont été mis à jour .

52
Blake Mumford

C’est ma solution pour EF Core 2.0, adaptée de https://docs.Microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database -initialisation-code

Dans program.cs

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Seed().Run();
    }

....

Puis ma classe de semoir

public static class DatabaseSeedInitializer
{
    public static IWebHost Seed(this IWebHost Host)
    {
        using (var scope = Host.Services.CreateScope())
        {
            var serviceProvider = scope.ServiceProvider;

            try
            {
                Task.Run(async () =>
                {
                    var dataseed = new DataInitializer();
                    await dataseed.InitializeDataAsync(serviceProvider);
                }).Wait();

            }
            catch (Exception ex)
            {
                var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }
        return Host;
    }
}
13
Labi

tl; dr : jetez un coup d'œil sur mon projet dwCheckApi pour voir comment je l'ai mis en œuvre.

Comme d'autres l'ont déjà dit, vous pouvez lire vos données de départ à partir de JSON ou similaire (vous pouvez ainsi contrôler la source si vous le souhaitez).

La façon dont je l'ai implémenté dans mes projets est d'avoir une méthode appelée dans la méthode Configure de la classe Startup (uniquement en cours de développement):

if (env.IsDevelopment())
{
  app.EnsureDatabaseIsSeeded(false);
}

qui appelle ce qui suit:

public static int EnsureDatabaseIsSeeded(this IApplicationBuilder applicationBuilder,
 bool autoMigrateDatabase)
{
    // seed the database using an extension method
    using (var serviceScope = applicationBuilder.ApplicationServices
   .GetRequiredService<IServiceScopeFactory>().CreateScope())
   {
       var context = serviceScope.ServiceProvider.GetService<DwContext>();
       if (autoMigrateDatabase)
       {
           context.Database.Migrate();
       }
       return context.EnsureSeedData();
   }
}

Mon DbContext est de type DwContext qui est une classe qui étend le type EF Core DbContext

La méthode d'extension EnsureSeedData ressemble à ceci:

public static int EnsureSeedData(this DwContext context)
{
    var bookCount = default(int);
    var characterCount = default(int);
    var bookSeriesCount = default(int);

    // Because each of the following seed method needs to do a save
    // (the data they're importing is relational), we need to call
    // SaveAsync within each method.
    // So let's keep tabs on the counts as they come back

    var dbSeeder = new DatabaseSeeder(context);
    if (!context.Books.Any())
    {
        var pathToSeedData = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookSeedData.json");
        bookCount = dbSeeder.SeedBookEntitiesFromJson(pathToSeedData).Result;
    }
    if (!context.BookCharacters.Any())
    {
        characterCount = dbSeeder.SeedBookCharacterEntriesFromJson().Result;
    }
    if (!context.BookSeries.Any())
    {
        bookSeriesCount = dbSeeder.SeedBookSeriesEntriesFromJson().Result;
    }

    return bookCount + characterCount + bookSeriesCount;
}

Cette application est destinée à montrer les relations entre les livres, les personnages et les séries. C'est pourquoi il y a trois semeurs.

Et l'une de ces méthodes de semis ressemble à ceci:

public async Task<int> SeedBookEntitiesFromJson(string filePath)
{
    if (string.IsNullOrWhiteSpace(filePath))
    {
        throw new ArgumentException($"Value of {filePath} must be supplied to {nameof(SeedBookEntitiesFromJson)}");
    }
    if (!File.Exists(filePath))
    {
        throw new ArgumentException($"The file { filePath} does not exist");
    }
    var dataSet = File.ReadAllText(filePath);
    var seedData = JsonConvert.DeserializeObject<List<Book>>(dataSet);

    // ensure that we only get the distinct books (based on their name)
    var distinctSeedData = seedData.GroupBy(b => b.BookName).Select(b => b.First());

    _context.Books.AddRange(distinctSeedData);
    return await _context.SaveChangesAsync();
}

Il y a probablement du code ici qui n'est pas génial, mais ce pourrait être un point de départ pour vous.

Les amorceurs n'étant appelés que dans l'environnement de développement, vous devez vous assurer que votre application démarre de cette façon (si vous démarrez à partir de la ligne de commande, vous pouvez utiliser ASPNETCORE_ENVIRONMENT=Development dotnet run pour s’assurer qu’il commence en développement).

Cela signifie également que vous aurez besoin d'une approche différente pour créer votre base de données en production. Dans dwCheckApi, j'ai un contrôleur qui peut être appelé pour initialiser la base de données (regardez la méthode SeedData de DatabaseController pour voir comment je le fais).

6
Jamie Taylor

Je n'aime pas l'approche HasData qui a été écrite dans la documentation Microsoft car je ne peux pas garder mes migrations propres de cette façon et parce que OnModelCreating() dans mon DbContext commence à dépendre de données qui se sentent un peu faux et provoque des problèmes avec le générateur de données aléatoires.

Pour moi, le moyen le plus efficace et le plus confortable est de créer une classe de départ pour chacun de mes ensembles de base de données qui ressemble à ceci. (Avec la bibliothèque Bogus, c'est aussi simple que de respirer)

using Bogus;

        // namespace, class, etc.


        // CategorySeeder seed method
        public int Seed(AppDbContext context)
        {


            var faker = new Faker<Category>()
                .RuleFor(r => r.IsGroup, () => true)
                .RuleFor(r => r.Parent, () => null)
                .RuleFor(r => r.UniversalTimeTicks, () => DateTime.Now.ToUniversalTime().Ticks)
                .RuleFor(r => r.Title, f => "Folder: " + f.Random.Word());

            var folders1 = faker.Generate(5);

            faker.RuleFor(r => r.Parent, () => folders1.OrderBy(r => Guid.NewGuid()).First());
            var folders2 = faker.Generate(10);
            var folders3 = folders1.Concat(folders2).ToArray();

            faker.RuleFor(r => r.Parent, () => folders3.OrderBy(r => Guid.NewGuid()).First());
            faker.RuleFor(r => r.Title, f => f.Random.Word());
            faker.RuleFor(r => r.IsGroup, () => false);

            var elements = faker.Generate(20);

            var allSeeds = elements.Concat(folders3).ToArray();

            context.AddRange(allSeeds);
            context.SaveChanges();
            return allSeeds.Length;
        }

        // ProductSeeder Seed method
        public int Seed(AppDbContext context)
        {
            var faker = new Faker<Product>()
                .RuleFor(r => r.Sku, f => f.Random.AlphaNumeric(8))
                .RuleFor(r => r.Title, f => f.Random.Word())
                .RuleFor(r => r.Category, () => context.Categories.Where(c => !c.IsGroup).OrderBy(o => Guid.NewGuid()).First());

            var prod = faker.Generate(50);
            context.AddRange(prod);
            context.SaveChanges();
            return prod.Count;
        }

Créez ensuite le contrôleur de service, qui ne fonctionne que dans l’environnement de développement.

    public class DataGeneratorController : BaseController
    {
        public DataGeneratorController(IServiceProvider sp) : base(sp) { }

        public IActionResult SeedData()
        {
            var lst = new List<string>();

            if (!_dbContext.Categories.Any())
            {
                var count = new CategoryConfiguration().Seed(_dbContext);
                lst.Add($"{count} Categories have been seeded.");
            }

            if (!_dbContext.Products.Any())
            {
                var count = new ProductConfiguration().Seed(_dbContext);
                lst.Add($"{count} Products have been seeded.");
            }

            if (lst.Count == 0)
            {
                lst.Add("Nothing has been seeded.");
            }

            return Json(lst);
        }
    }

Et appelez-le de Insomnia\Postman quand je veux.

4
Liam Kernighan

J'ai créé mes graines en json, et je les ai ajoutées par lot à mon coeur Asp.net Startup

Très similaire à https://garywoodfine.com/how-to-seed-your-ef-core-database/

Vous n'avez pas encore trouvé de solution prête à l'emploi.

0
Calin

Je suis tombé sur la même question et j'ai corrigé le semis de la manière suivante:

J'ai d'abord ajouté le public static bool AllMigrationsApplied(this DbContext context) de garywoodfine à mon modèle.

Ensuite, j'ai implémenté une portée de service pour créer la base de données -> voir ce blog

Puis j'ai fait un public static void EnsureSeedData avec le code permettant de générer des données de test à l'aide de NBuilder et Faker en suivant le tutoriel sur ce blog

J'espère que cela aidera les gens à mettre en place une graine de test automatisée pour leurs projets. Actuellement, je suis occupé à implémenter ceci moi-même, quand j'aurai le temps, je posterai quelques exemples de code sur la façon de procéder.

0
botenvouwer

Au cas où une personne serait toujours intéressée par ce sujet, nous avons créé un ensemble d’outils (un outil global .net principal et une bibliothèque) qui simplifie le processus d’ensemencement des données.

En résumé: vous pouvez enregistrer le contenu de votre base de données actuelle dans des fichiers JSON ou XML, puis ajouter à votre application quelques lignes de code qui chargent ces fichiers et importer les données enregistrées dans votre base de données. L'ensemble d'outils est totalement gratuit et open-source .

Les instructions détaillées étape par étape sont les suivantes: publié ici .

0
Sergiy