How to sow in Entity Framework Core 2?

I have two tables, and I want to fill them using seeds.

I am using ASP.NET Core 2 on Ubuntu.

How to fill in data for two tables, where one is connected to the other through a foreign key?

The flowmeter has many notes, and the note belongs to the flowmeter.

I want to do something like this, but it should be stored in a database:

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 } } } 
+36
source share
7 answers

Starting with Entity Framework Core 2.1, there is now a new method for populating data. In your DbContext override of the OnModelCreating class:

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

And for related entities, use anonymous classes and specify the foreign key of the related entity:

 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: Note that you will need to migrate the add-in after entering this data into the OnModelCreating and Update-Database methods to update your data.

Official documents have been updated .

+53
source

This is my solution for EF Core 2.0, adapted from https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code

In the program .cs

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

....

Then my seeder class

 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
source

tl; dr . Check out my dwCheckApi project to find out how I implemented it.

As others have said, you can read your initial data from JSON or similar (this way, if you want, you can control the source code).

The way I implemented this in my projects is to have a method that is called in the Configure method in the Startup class (only at design time):

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

which causes the following:

 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(); } } 

My DbContext is of type DwContext , which is a class extending the EF Core DbContext

The EnsureSeedData extension method is as follows:

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

This app is designed to show the relationships between books, characters and TV shows. That is why there are three seeders.

And one of these seeder methods looks like this:

 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(); } 

There may be some code here that is not very good, but it can be the starting point for a rebound.

Since seeders are called only in the development environment, you need to make sure that your application starts this way (if you start from the command line, you can use ASPNETCORE_ENVIRONMENT=Development dotnet run to make sure that it starts in development).

It also means that you will need a different approach to populating your database in a production environment. In dwCheckApi, I have a controller that can be called to populate the database (see how I do it, DatabaseController SeedData method ).

+6
source

I donโ€™t like the HasData approach, which was written in the Microsoft documentation, because I canโ€™t keep my migrations clean in this way and because OnModelCreating() in my DbContext starts to depend on data that seems a little wrong and causes problems with the random data generator.

For me, the most efficient and convenient way is to create an initial class for each of my DbSets, which looks like this. (With the Bogus library, it's as easy as breathing)

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

Then create a service controller that works only in the development environment.

  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); } } 

And call from Insomnia / Postman when I want.

+4
source

I created my seeds in json and just add them to my main Asp.net launch

Very similar to https://garywoodfine.com/how-to-seed-your-ef-core-database/

Could not find a ready-made solution.

0
source

I came across the same question and I set the seeding as follows:

First I added a public static bool AllMigrationsApplied(this DbContext context) from garywoodfine to my model.

Then I applied the service area to sow db -> see this blog

Then I made a public static void EnsureSeedData with code to generate test data using NBuilder and Faker, following this blog manual

I hope this helps people implement an automated test seed for their projects. I am currently implementing this myself, when I have time, I will post some code examples on how to do this.

0
source

If someone else is interested in this topic, we have created a set of tools (global .net tool and library) that simplify the process of filling data.

In short: you can save the contents of your current database into several JSON or XML files, and then add a few lines of code to your application that loads these files and imports the data stored there into your database. The toolkit is completely free and open source .

Detailed step-by-step instructions are published here .

0
source

Source: https://habr.com/ru/post/1269906/


All Articles