When someone downloads a project, the first five minutes decide whether they keep going. An empty store with a blank product page is a bad first impression. A store with categories, pictures and 171 products looks like the real thing.
So eCommerceMVCCore, my ASP.NET Core e-commerce project, creates and seeds its database on first run. Here's how to do it without duplicate rows, race conditions or a demo password left in production. The code is simplified.
Migrations first, then seed
Run migrations at startup, then seed only if the data isn't there yet:
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var db = services.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
await DemoData.SeedAsync(db,
services.GetRequiredService<UserManager<AppUser>>(),
services.GetRequiredService<RoleManager<IdentityRole>>());
}
app.Run();
MigrateAsync creates the database if it doesn't exist, so there's no separate "create database" step.
Make every seed step idempotent
Each step checks before it inserts. Running the app twice must never create two copies of anything:
public static async Task SeedAsync(AppDbContext db, UserManager<AppUser> users, RoleManager<IdentityRole> roles)
{
foreach (var role in new[] { "Administrator", "Moderator", "User" })
if (!await roles.RoleExistsAsync(role))
await roles.CreateAsync(new IdentityRole(role));
if (await users.FindByNameAsync("admin") is null)
{
var admin = new AppUser { UserName = "admin", Email = "admin@example.com", EmailConfirmed = true };
await users.CreateAsync(admin, "Change_Me_123!");
await users.AddToRoleAsync(admin, "Administrator");
}
if (!await db.Categories.AnyAsync())
{
db.Categories.AddRange(DemoCatalog.Categories());
await db.SaveChangesAsync();
}
if (!await db.Products.AnyAsync())
{
db.Products.AddRange(DemoCatalog.Products(db.Categories.ToList()));
await db.SaveChangesAsync();
}
}
Roles and users go through Identity's managers, not straight into the tables. That way passwords are hashed and normalised names are set correctly.
HasData or runtime seeding?
EF Core's HasData puts seed rows into migrations. It's great for small, fixed lookup data, such as order statuses. For demo data it's a poor fit:
- Every change to the demo catalog produces a new migration, with every row in it.
- It needs fixed primary keys for every row.
- It can't hash passwords or call services.
For 171 products with pictures and translations, runtime seeding is much easier to maintain.
EF Core 9 also added UseSeeding and UseAsyncSeeding on the DbContext options, which run as part of EnsureCreated and Migrate. It's a tidy place for the same idempotent code, if you'd rather keep it next to the context configuration.
Keep the demo data out of C
I keep the catalog in a JSON file that ships with the app and load it in DemoCatalog. A non-developer can edit product names and prices, and the C# stays short. Pictures go in wwwroot with matching file names.
See the result: the ASP.NET Core store demo starts with categories, pictures and 171 demo products, straight from the first run.
Watch out for multiple instances
If two instances of the app start at the same time (two containers, or a scaled-out web app), both can run the "is it empty?" check before either inserts. For production, run migrations as a separate deployment step rather than at app startup. For a demo or a single instance, startup seeding is fine.
The demo admin is a demo account
A seeded admin with a known password is perfect locally and dangerous in production. My go-live checklist starts with "change or remove the demo admin". Better still, read the initial admin password from configuration, or skip creating the admin unless an environment variable asks for it.
FAQ
Should I use HasData or custom seeding in EF Core?
Use HasData for small, fixed lookup data that belongs in migrations. Use runtime seeding, or UseSeeding / UseAsyncSeeding in EF Core 9 and later, for demo data, large catalogs and Identity users.
How do I seed Identity users and roles in ASP.NET Core?
Create a scope at startup, resolve RoleManager and UserManager, and create each role and user only if it doesn't exist yet. Going through the managers hashes passwords and sets normalised names correctly.
Should I run EF Core migrations at application startup?
For a single instance or a demo, yes. With several instances in production, apply them as a deployment step, for example with a migration bundle or a SQL script. eCommerceMVCCore migrates on startup, so the first run needs no setup.
Get the source code
Database creation and seeding on first run, for SQL Server, PostgreSQL, MySQL and SQLite, are built into eCommerceMVCCore. Get the ASP.NET Core 10 e-commerce source code and have a working store running in minutes.
Do you seed in code, from JSON or with SQL scripts? I'd like to know what works for you.
Top comments (0)