Two battle-tested seeding strategies in one runnable sample β Model-based
HasDatafor static reference data and custom runtime seeders with full idempotency guards for dynamic datasets.
If this sample saved you time, consider joining our Patreon community. You'll get exclusive .NET tutorials, premium code samples, and early access to new content β all for the price of a coffee.
π Join CodingDroplets on Patreon
Prefer a one-time tip? Buy us a coffee β
- How to use model-based
HasDataseeding for static reference data (roles, categories, lookup tables) - How to build custom runtime seeders with
IDataSeederfor dynamic or large datasets - How to write idempotency guards so seeders are safe to run on every startup
- How to orchestrate multiple seeders in a controlled order with
DatabaseSeeder - How to switch from InMemory to SQL Server with a two-line change
| Strategy | Best For | Pros | Cons |
|---|---|---|---|
Model-based HasData |
Static reference data (lookup tables, roles, categories) | Migrations-aware, zero startup overhead, deterministic | Hard-coded IDs required; large datasets inflate migration files |
| Custom Runtime Seeders | Dynamic/computed data, large datasets, external source data | Full C# expressiveness, no migration bloat, DB-generated IDs | Runs at startup; must guard against duplicates |
App Startup (Program.cs)
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DatabaseSeeder β
β Orchestrates all IDataSeeder implementations β
β in order (Order property) β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Strategy 1: AppDbContext.OnModelCreating β β
β β modelBuilder.Entity<T>().HasData(...) β β
β β β Applied by EF migrations / EnsureCreated β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Strategy 2: ProductSeeder : IDataSeeder β β
β β β Guard: AnyAsync() check before inserting β β
β β β Resolves FK by slug (no hard-coded IDs) β β
β β β SaveChangesAsync() β β
β ββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
Database ready with seeded reference + product data
efcore-data-seeding-seeddata/
βββ src/
β βββ EFCore.DataSeeding.Api/
β βββ Controllers/
β β βββ ProductsController.cs # REST endpoints to verify seeded data
β βββ Data/
β β βββ AppDbContext.cs # DbContext with HasData configuration
β β βββ Seeding/
β β βββ IDataSeeder.cs # Seeder marker interface
β β βββ DatabaseSeeder.cs # Orchestrates all seeders in order
β β βββ ProductSeeder.cs # Custom runtime seeder (Strategy 2)
β βββ Models/
β β βββ Category.cs
β β βββ Product.cs
β βββ Repositories/
β β βββ IProductRepository.cs
β β βββ ProductRepository.cs # Repository pattern with AsNoTracking
β βββ Program.cs # DI wiring + startup seeding
βββ tests/
βββ EFCore.DataSeeding.Tests/
βββ HasDataSeedingTests.cs # Tests for Strategy 1
βββ ProductSeederTests.cs # Tests for Strategy 2 (idempotency verified)
- .NET 10 SDK (or .NET 8/9 β update
TargetFrameworkin.csproj) - Any IDE: Visual Studio 2022+, VS Code, or JetBrains Rider
# Clone the repo
git clone https://github.com/codingdroplets/efcore-data-seeding-seeddata.git
cd efcore-data-seeding-seeddata
# Run (uses InMemory database β no SQL Server setup needed)
dotnet run --project src/EFCore.DataSeeding.Api
# Open Swagger UI β https://localhost:{port}/swaggerConfigured inside OnModelCreating in AppDbContext.cs. Applied automatically by EF Core on EnsureCreated or migration:
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, Slug = "electronics", Name = "Electronics" },
new Category { Id = 2, Slug = "books", Name = "Books" },
new Category { Id = 3, Slug = "clothing", Name = "Clothing" }
);
β οΈ Important:HasDatarequires hard-coded primary keys. Navigation properties are not supported β use flat scalar values only.
ProductSeeder implements IDataSeeder and is called at startup. It uses an idempotency guard so it is safe to run on every startup:
public async Task SeedAsync(CancellationToken cancellationToken = default)
{
// Guard: skip if already seeded
bool alreadySeeded = await _db.Products
.AnyAsync(p => p.Stock > 500, cancellationToken);
if (alreadySeeded) return;
// Resolve FK by slug β no hard-coded IDs
var electronics = await _db.Categories
.FirstAsync(c => c.Slug == "electronics", cancellationToken);
await _db.Products.AddRangeAsync(new List<Product>
{
new() { Name = "USB-C Hub (7-in-1)", Price = 49.99m, Stock = 750, CategoryId = electronics.Id },
// ...
}, cancellationToken);
await _db.SaveChangesAsync(cancellationToken);
}builder.Services.AddScoped<IDataSeeder, ProductSeeder>();
builder.Services.AddScoped<DatabaseSeeder>();Add more seeders by implementing IDataSeeder and registering them. Control execution order via the Order property.
The sample uses InMemory for zero-friction local runs. To use SQL Server:
- Update
Program.cs:builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
- Replace
EnsureCreatedAsync()withMigrateAsync(). - Add your connection string to
appsettings.json.
dotnet test -c Release| Test | Strategy Covered |
|---|---|
HasData_Seeds_ThreeCategories |
Strategy 1 |
HasData_Seeds_ElectronicsCategory_WithCorrectSlug |
Strategy 1 |
HasData_Seeds_FourBaseProducts |
Strategy 1 |
HasData_Products_HaveCorrectCategoryAssignments |
Strategy 1 |
ProductSeeder_InsertsHighStockProducts |
Strategy 2 |
ProductSeeder_IsIdempotent_DoesNotDuplicate |
Strategy 2 |
ProductSeeder_AssignsCorrectCategories |
Strategy 2 |
- EF Core β Data Seeding (Microsoft Docs)
- EF Core β Migrations Overview
- EF Core β InMemory Database Provider
This project is licensed under the MIT License.
| Platform | Link |
|---|---|
| π Website | https://codingdroplets.com/ |
| πΊ YouTube | https://www.youtube.com/@CodingDroplets |
| π Patreon | https://www.patreon.com/CodingDroplets |
| β Buy Me a Coffee | https://buymeacoffee.com/codingdroplets |
| π» GitHub | http://github.com/codingdroplets/ |
Want more samples like this? Support us on Patreon or buy us a coffee β β every bit helps keep the content coming!