Skip to content

codingdroplets/efcore-data-seeding-seeddata

Repository files navigation

EF Core Data Seeding: Production-Ready Patterns for .NET

Two battle-tested seeding strategies in one runnable sample β€” Model-based HasData for static reference data and custom runtime seeders with full idempotency guards for dynamic datasets.

.NET EF Core License: MIT Visit CodingDroplets YouTube Patreon Buy Me a Coffee GitHub


πŸš€ Support the Channel β€” Join on Patreon

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 β˜•


🎯 What You'll Learn

  • How to use model-based HasData seeding for static reference data (roles, categories, lookup tables)
  • How to build custom runtime seeders with IDataSeeder for 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

πŸ“‹ Seeding Strategies Compared

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

πŸ—ΊοΈ Architecture Overview

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

πŸ“ Project Structure

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)

πŸ› οΈ Prerequisites

  • .NET 10 SDK (or .NET 8/9 β€” update TargetFramework in .csproj)
  • Any IDE: Visual Studio 2022+, VS Code, or JetBrains Rider

⚑ Quick Start

# 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}/swagger

πŸ”§ How It Works

Strategy 1 β€” Model-Based HasData (Static Reference Data)

Configured 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: HasData requires hard-coded primary keys. Navigation properties are not supported β€” use flat scalar values only.

Strategy 2 β€” Custom Runtime Seeder (Dynamic Data)

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

Register Seeders in Program.cs

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.


πŸ”„ Switching to SQL Server

The sample uses InMemory for zero-friction local runs. To use SQL Server:

  1. Update Program.cs:
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
  2. Replace EnsureCreatedAsync() with MigrateAsync().
  3. Add your connection string to appsettings.json.

πŸ§ͺ Running Tests

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

πŸ“š References


πŸ“„ License

This project is licensed under the MIT License.


πŸ”— Connect with CodingDroplets

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!

About

Production-ready EF Core data seeding patterns for .NET: model-based HasData seeding and custom runtime seeders with idempotency guards, repository pattern, and full test coverage.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages