EF Core - Seed Data Strategy
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:
- Every entity needs its primary key set. That's how change detection works: it checks whether each record exists and whether any fields have changed.
- Foreign key values must be set explicitly.
- If a seeded record's primary key changes, the old record is removed. That makes sense, since nothing can refer to it any more.
- Depending on your scenario, you may still want custom initialisation logic, such as temporary data for testing.
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:
- Generate a list of user details with a library such as Faker.
- Iterate through the list and assign each user a hard-coded GUID from a list you've created.
- Hash each password with
PasswordHasher. - Pass the completed list to
HasData. - Copy the data from the
AspNetUserstable as JSON. I used SQL Operations Studio. - 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.