EF Core - Seed Data Strategy

· 2 min

EF Core 2.1 introduced a relatively new way to seed entity data, and it's fantastic.

Unlike in EF6, seed data is tied to an entity type and tracked by migrations. Add or edit seed data for any entity, and the next migration computes the inserts or updates needed to push the change to the database. You get all the change tracking migrations already provide.

Let's see some code

First, override OnModelCreating in your DbContext. In an existing project, you're probably already doing this.

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }

Next, call HasData with a populated instance of an entity:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.Entity<Person>().HasData(new Person { PersonId = 1, Name = "Ash" });
    }

Before running add-migration, a few key points:

Running add-migration firstseed now produces a migration that inserts the new records, since their primary keys aren't in the table yet:

migrationBuilder.InsertData(
    table: "Persons",
    columns: new[] { "PersonId", "Name" },
    values: new object[] { 1, "Ash" });

Any later change to the data produces a new migration with the right insert, update or delete. Seed data evolves with the application, instead of living in one monolithic script of inserts.

Further points

Structuring seed data

Seed data in OnModelCreating gets hard to manage with dozens of entities. I prefer extension methods to keep it clean:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.AddContractHoursData();
        builder.AddLocationData();
        builder.AddJobTitleData();
    }

I also gave each entity's data its own self-contained class. That follows the Single Responsibility Principle, at some cost to DRY:

    public static void AddRewardCategoryData(this ModelBuilder model)
    {
        model.Entity<RewardCategory>().HasData(new List<RewardCategory>()
        {
            new RewardCategory()
            {
                RewardCategoryId = 1,
                Name = "Vouchers",
                Enabled = true
            },
            new RewardCategory()
            {
                RewardCategoryId = 2,
                Name = "Charity",
                Enabled = true
            }
        });
    }
Creating Identity users

A question I've seen asked is how to seed Identity users, since UserManager normally handles things like password hashing.

Identity's PasswordHasher can hash the password, but it generates a different hash on every run, which bloats every migration with updates. You'd also rather not hard-code dozens or hundreds of User models by hand.

My approach:

  1. Generate a list of user details with a library such as Faker.
  2. Iterate through the list and assign each user a hard-coded GUID from a list you've created.
  3. Hash each password with PasswordHasher.
  4. Pass the completed list to HasData.
  5. Copy the data from the AspNetUsers table as JSON. I used SQL Operations Studio.
  6. Use find and replace with regular expressions to fix the formatting and turn it into a list of users.

You end up with fully populated entities, all hard-coded, without tedious data entry.