How to seed in Entity Framework Core 2?
Asked Answered
C

9

55

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

I use ASP.NET Core 2 in Ubuntu.

How to populate the data for the two tables where one is connected to the other via a foreign key?

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

I want to do something like this, but it should be stored in the 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 }
    }
}
Cheryle answered 17/7, 2017 at 15:42 Comment(2)
As I know the best solution in .net core is create a console project to seed your dbRabbitry
Very simple and useful solution: EF Core Seed dataChafee
G
74

As of Entity Framework Core 2.1 there is now a new method of seeding data. In your DbContext class override OnModelCreating:

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: Please note you will need to run an add-migration after you enter this data in your OnModelCreating method and Update-Database to update your data.

The official docs have been updated.

Grath answered 24/5, 2018 at 5:1 Comment(12)
how would you do a conditional seed? we have the need to seed a root node in a tree but only the first time the DB is created never after, currently we have a class and a method that checks if data exist then don't seed.Norvil
@Matt data seeding is now a migration step. As long as you're using migrations correctly, data won't be seeded again! This was one of the main reasons we had to do it differently than in EF6.Ion
@MartínColl tried to find updated docs but most use old solutions any pointers to new docs that describes this in more details ?Norvil
You should be looking at the HasData method: learn.microsoft.com/en-us/ef/core/modeling/data-seedingIon
PLEASE put this in the comment that you NEED to run an ADD-MIGRATION after you enter this data in your OnModelCreating method and Update-Database to update your data!!Cw
@blake Related question: how can I check if I've entered the OnModelCreating() in "migration mode" or not? I don't want that every time the app is run the seed data is generated in memory and then discarded.Venesection
Sorry how would you add a HasKey to your blog table if you want to seed it as well?Ngo
@MartínColl how would you seed an enum? I was following this article's solution, but it doesn't really apply in EF Core 2.1: #34558074Dorsey
@Dorsey first you have to model your entities to support enums: learn.microsoft.com/en-us/ef/core/modeling/value-conversions. Then, just use the enum values in the HasData method. This should work unless there's a limitation that I'm not aware of.Ion
@MartínColl I don't think you can use enum values in the HasData method because when you call modelBuilder.Entity<Post>() for example, Post has to be a reference type where as an enum is a value type. Further, I already setup value converters to convert class properties that are enums to be converted into strings when stored in the DB. While this takes care of an enum being stored as a string column of some parent table, this doesn't seed the actual enums into their own SQL lookup tables.Dorsey
You can seed enum values to a property of an entity. If you're trying to create a table for your enum, where the rows are each enum value, then that's a different question. I suggest you create a new question (and maybe ping me so I can take a look).Ion
This doesn't work too well if you want to seed a lot of data, I'm getting OutOfMemoryException's when I try and run it.Wylie
A
15

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

In 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;
    }
}
Appolonia answered 26/12, 2017 at 10:28 Comment(4)
I do love how you have managed to clean up the code and make it feel a lot more integrated. I didn't like how the standard boilerpoint code that is normally there just dumps it all into the program Main. :)Louisiana
wouldnt it make sense to re-throw the error after logging?Tenement
Can we see your DataInitializer class? Thanks!Sarcenet
public class DataInitializer { private ApplicationDbContext _context; //... //... //... public async Task InitializeDataAsync(IServiceProvider serviceProvider) { _context = serviceProvider.GetService<ApplicationDbContext>(); //... //... //seed data } }Appolonia
C
11

tl;dr: Take a look through my dwCheckApi project to see how I've implemented it.

As others have said, you can read your seed data from JSON or similar (that way it can be source controlled, if you want).

The way that I've implemented it in my projects is to have a method which is called in the Configure method in the Startup class (only when in development):

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

which calls 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 which extends the EF Core DbContext type

The EnsureSeedData extension method looks like this:

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

This application is meant to show the relationships between books, characters and series. Which is why there are three seeders.

And one of those 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's probably some code here which isn't great, but it could be a starting point for you to bounce off of.

Because the seeders are only called when in the development environment, you'll need to ensure that your application starts that way (if starting from the command line you can use ASPNETCORE_ENVIRONMENT=Development dotnet run to ensure that it starts in development).

It also means that you'll need a different approach to seeding your database in production. In dwCheckApi, I have a controller which can be called to seed the database (take a look at the DatabaseController's SeedData method to see how I do that).

Celestecelestia answered 22/11, 2017 at 10:21 Comment(0)
P
8

I don't like the HasData approach than has been written in Microsoft documentation because I cannot keep my migrations clean this way & because OnModelCreating() in my DbContext starts to depend on data which feels a bit wrong and causes issues with random data generator.

For me the most efficient and comfortable way is to create a seed class for each of my DbSets that looks like this. (With 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 the service controller, that works only in 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 it from Insomnia\Postman whenever I want.

Pencel answered 9/1, 2019 at 21:30 Comment(0)
S
4

Create seed data static class like

 public static class SeedData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            var context = serviceProvider.GetRequiredService<YourDbContext>();
            context.Database.EnsureCreated();
            if (!context.Items.Any())
            {
                context.Items.Add(entity: new Item() { Name = "Green Thunder" });
                context.Items.Add(entity: new Item() { Name = "Berry Pomegranate" });
                context.Items.Add(entity: new Item() { Name = "Betty Crocker" });
                context.Items.Add(entity: new Item() { Name = "Pizza Crust Mix" });

                context.SaveChanges();
            }

            if (!context.Shoppings.Any()) {
                context.Shoppings.Add(entity:new Shopping() { Name="Defualt" });
            }
        }
    }

update your program.cs code for inserting your seed data like below

 public class Program
    {
        public static void Main(string[] args)
        {

            //CreateWebHostBuilder(args).Build().Run();
            var host = CreateWebHostBuilder(args).Build();

            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;

                try
                {
                    var context = services.GetRequiredService<YourDbContext>();
                    context.Database.Migrate(); // apply all migrations
                    SeedData.Initialize(services); // Insert default data
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred seeding the DB.");
                }
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
Superstar answered 10/12, 2019 at 8:42 Comment(1)
This seems the most reasonable way to seed data for me.Crisper
A
1

In case somebody still interested in this topic, we've created a set of tools (a .net core global tool and a library) which simplifies the process of data seeding.

Long story short: you can save the content of your current DB to some JSON or XML files and then add to your app a few lines of code that loads those files and import the data saved there to your DB. The toolset is totally free and open-source.

The detailed step-by-step instructions are published here.

Appellant answered 12/8, 2019 at 6:34 Comment(0)
F
0

I created my seeds in json, and just batch add them my Asp.net core Startup

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

Did not find an out of the box solution yet.

Flaky answered 14/9, 2017 at 15:37 Comment(0)
D
0

I came across the same question and I fixed the seeding in the following way:

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

Then I implemented a service scope to seed the db -> see this blog

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

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

Doenitz answered 21/11, 2017 at 14:12 Comment(0)
T
0

I'm using Entity Framework 3 with an "In Memory Database" context and was able to seed data by doing the following.

  1. Override OnModelCreating in my DbContext class. For example:
    public class NoteContext : DbContext
    {
        public DbSet<Note> Notes { get; set; }

        public NoteContext(DbContextOptions<NoteContext> options)
            : base(options)
        {
        }

        /// <summary>
        /// Seed data
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Note>().HasData(new[] {
                new Note { NoteId = Guid.NewGuid(), User = "UserA", Message = "Message from UserA" },
                new Note { NoteId = Guid.NewGuid(), User = "UserB", Message = "Message from UserB" }
            });
        }
    }
  1. Call context.Database.EnsureCreated() before using the context. For example:
    [Route("api/[controller]")]
    [ApiController]
    public class NotesController : ControllerBase
    {
        private readonly NoteContext _context;

        public NotesController(NoteContext context)
        {
            _context = context;

            // Seed data
            _context.Database.EnsureCreated();
        }
    ...
Trifolium answered 19/8, 2020 at 2:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.