HomeNikola Knezevic

In this article

Banner

Seeding Data with EF Core 9

14 Aug 2025
5 min

Special Thanks to Our Sponsors:

Sponsor Logo

EF Core is too slow? Discover how you can easily insert 14x faster (reducing saving time by 94%).

Boost your performance with our method within EF Core: Bulk Insert, update, delete and merge.

Thousands of satisfied customers have trusted our library since 2014.

👉 Learn more

Sponsor Newsletter

Data seeding is the process of inserting predefined data during application setup or initialization.

It’s especially valuable for testing and for ensuring the essential tables and records our application needs are available from the start. This approach guarantees consistency across environments and speeds up development.

If you’re using EF Core, you’re in luck, it recently introduced yet another way to perform seeding and I love it!

Data Seeding with EF Core

EF Core offers 4 ways to add seeding data:

  • Custom initialization logic
  • Model managed data
  • Manual migration
  • Data seeding through configuration options

In this post, we’ll focus solely on data seeding using configuration options, a new feature introduced in EF Core 9.

If you’re on an older version and don’t plan to upgrade soon, don’t worry, I'll be covering the other seeding methods shortly as well.

Getting Started

For this example, we’ll use a simple .NET 9 API that performs CRUD operations on a Product entity:

csharp
public sealed class Product
{
    public Guid Id { get; set; }

    public DateTime CreatedAt { get; set; }

    public DateTime? ModifiedAt { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

    public decimal Price { get; set; }
}

UseSeeding and UseSeedingAsync

EF Core 9 introduces two new methods to simplify data seeding:

  • UseSeeding
  • UseAsyncSeeding

Both serve the same purpose, the only difference is whether the seeding logic runs synchronously or asynchronously.

You can configure them wherever you set up your DbContext (e.g. Program.cs or extension methods):

csharp
services.AddDbContext<AppDbContext>(options =>
    options
        .UseNpgsql(configuration.GetConnectionString("Postgres"))
        .UseAsyncSeeding(async (dbContext, _, cancellationToken) =>
        {
            if (!(await dbContext.Set<Product>().AnyAsync(cancellationToken)))
            {
                var products = GenerateProducts();

                dbContext.Set<Product>().AddRange(products);

                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }));

To keep things tidy, I created the helper method that generates fake products using Bogus:

csharp
private static IEnumerable<Product> GenerateProducts()
{
    var faker = new Faker<Product>()
        .RuleFor(x => x.Id, f => f.Random.Guid())
        .RuleFor(x => x.CreatedAt, f => f.Date.Past())
        .RuleFor(x => x.Name, f => f.Commerce.ProductName())
        .RuleFor(x => x.Description, f => f.Commerce.ProductDescription())
        .RuleFor(x => x.Price, f => f.Random.Decimal());

    return faker.Generate(10);
}

Bogus helps me generate realistic test data. If you want to learn more about Bogus, check my blog post on it: Generate Realistic Fake Data in C#

First, we check if the table already contains any records, preventing duplicate seeding.

If no records exist, we generate a list of products and insert them.

Notice how flexible this approach is, this approach defitely gives you flexibility covering more complex seeding scenarios as well.

The primary key is generated here by Bogus, but you could also let the database generate it if you prefer.

Triggering Seeding

To trigger the seeding logic we’ll use EnsureCreatedAsync() in the app:

csharp
await using var scope = builder.ApplicationServices.CreateAsyncScope();

await using var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

await dbContext.Database.EnsureCreatedAsync();

It's important to note that EnsureCreated() triggers the synchronous seeding method (UseSeeding).

EnsureCreatedAsync() triggers the asynchronous seeding method (UseAsyncSeeding).

Mixing synchronous and asynchronous methods will prevent your seeding logic from running, so be careful to use the matching pair.

Additionaly, for production environments, it’s highly recommended to separate seeding into its own application or container rather than running it inside your main app.

This approach helps avoid concurrency issues and potential deadlocks when multiple instances start simultaneously.

Conclusion

With EF Core 9’s new UseSeeding and UseAsyncSeeding methods, seeding has become more flexible and straightforward than ever.

You can write normal C# code to seed your data asynchronously or synchronously, query the database during seeding, and even integrate complex logic if needed.

This approach is by far my favorite way of seeding data.

Just remember to match your seeding method with the corresponding EnsureCreated() or EnsureCreatedAsync() call.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.