From 0e44b0f276b72ee3a16c86e51b1fa158537dbd8c Mon Sep 17 00:00:00 2001 From: AJ Matthews Date: Tue, 30 Sep 2025 15:40:53 +0100 Subject: [PATCH 1/3] Added a discussion of UseSeeding and UseAsyncSeeding to the Data Seeding article. --- docs/database/seed-database-data.md | 183 ++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/docs/database/seed-database-data.md b/docs/database/seed-database-data.md index 8e1998ee6f..054b466468 100644 --- a/docs/database/seed-database-data.md +++ b/docs/database/seed-database-data.md @@ -97,6 +97,162 @@ If you run this code, you can use the PHP My Admin resource to check that a tabl ## Seed data using EF Core +Starting with EF Core 9.0, you can use the and methods to configure database seeding directly during context configuration. This approach is cleaner than manually running migrations during startup and integrates better with EF Core's lifecycle management. + +> [!IMPORTANT] +> These types of configurations should only be done during development, so make sure to add a conditional that checks your current environment context. + +### Seed data with UseSeeding and UseAsyncSeeding methods + +Add the following code to the _:::no-loc text="Program.cs":::_ file of your **API Service** project: + +### [SQL Server](#tab/sql-server) + +```csharp +builder.AddSqlServerDbContext("TicketsDB", configureDbContextOptions: options => +{ + if (builder.Environment.IsDevelopment()) + { + options.UseSeeding((context, _) => + { + var testTicket = context.Set().FirstOrDefault(t => t.Title == "Test Ticket 1"); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + context.SaveChanges(); + } + + }); + + options.UseAsyncSeeding(async (context, _, cancellationToken) => + { + var testTicket = await context.Set().FirstOrDefaultAsync(t => t.Title == "Test Ticket 1", cancellationToken); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + await context.SaveChangesAsync(cancellationToken); + } + }); + } +}); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +if (app.Environment.IsDevelopment()) +{ + // Ensure database is created and seeded + using (var scope = app.Services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(); + } +} +``` + +### [PostgreSQL](#tab/postgresql) + +```csharp +builder.AddNpgsqlDbContext("TicketsDB", configureDbContextOptions: options => +{ + if (builder.Environment.IsDevelopment()) + { + options.UseSeeding((context, _) => + { + var testTicket = context.Set().FirstOrDefault(t => t.Title == "Test Ticket 1"); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + context.SaveChanges(); + } + + }); + + options.UseAsyncSeeding(async (context, _, cancellationToken) => + { + var testTicket = await context.Set().FirstOrDefaultAsync(t => t.Title == "Test Ticket 1", cancellationToken); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + await context.SaveChangesAsync(cancellationToken); + } + }); + } +}); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +if (app.Environment.IsDevelopment()) +{ + // Ensure database is created and seeded + using (var scope = app.Services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(); + } +} +``` + +### [MySQL](#tab/mysql) + +```csharp +```csharp +builder.AddMySqlDbContext("TicketsDB", configureDbContextOptions: options => +{ + if (builder.Environment.IsDevelopment()) + { + options.UseSeeding((context, _) => + { + var testTicket = context.Set().FirstOrDefault(t => t.Title == "Test Ticket 1"); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + context.SaveChanges(); + } + + }); + + options.UseAsyncSeeding(async (context, _, cancellationToken) => + { + var testTicket = await context.Set().FirstOrDefaultAsync(t => t.Title == "Test Ticket 1", cancellationToken); + if (testTicket == null) + { + context.Set().Add(new SupportTicket { Title = "Test Ticket 1", Description = "This is a test ticket" }); + await context.SaveChangesAsync(cancellationToken); + } + }); + } +}); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +if (app.Environment.IsDevelopment()) +{ + // Ensure database is created and seeded + using (var scope = app.Services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + await context.Database.EnsureCreatedAsync(); + } +} +``` + +--- + +The `UseSeeding` and `UseAsyncSeeding` methods provide several advantages over manual seeding approaches: + +- **Integrated lifecycle**: Seeding is automatically triggered when the database is created or when migrations are applied. +- **Conditional execution**: The seeding logic only runs when the database is first created, preventing duplicate data on subsequent runs. +- **Better performance**: The seeding methods are optimized for bulk operations and integrate with EF Core's change tracking. +- **Cleaner code**: Seeding configuration is co-located with the context configuration, making it easier to maintain. + +### Seed data manually + You can also seed data in .NET Aspire projects using EF Core by explicitly running migrations during startup. EF Core handles underlying database connections and schema creation for you, which eliminates the need to use volumes or run SQL scripts during container startup. > [!IMPORTANT] @@ -121,6 +277,15 @@ if (app.Environment.IsDevelopment()) { var context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); + + // Manually seed data + if (!context.Tickets.Any()) + { + context.Tickets.AddRange( + new Ticket { Title = "Example Ticket", Description = "This is a sample ticket for testing" } + ); + context.SaveChanges(); + } } } ``` @@ -142,6 +307,15 @@ if (app.Environment.IsDevelopment()) { var context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); + + // Manually seed data + if (!context.Tickets.Any()) + { + context.Tickets.AddRange( + new Ticket { Title = "Example Ticket", Description = "This is a sample ticket for testing" } + ); + context.SaveChanges(); + } } } ``` @@ -163,6 +337,15 @@ if (app.Environment.IsDevelopment()) { var context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); + + // Manually seed data + if (!context.Tickets.Any()) + { + context.Tickets.AddRange( + new Ticket { Title = "Example Ticket", Description = "This is a sample ticket for testing" } + ); + context.SaveChanges(); + } } } ``` From 03aeaa2d415e48fc8165a2f32d4e23bc1ba38a7a Mon Sep 17 00:00:00 2001 From: Alistair Matthews Date: Tue, 30 Sep 2025 15:56:34 +0100 Subject: [PATCH 2/3] Update docs/database/seed-database-data.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/database/seed-database-data.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/database/seed-database-data.md b/docs/database/seed-database-data.md index 054b466468..c151eac786 100644 --- a/docs/database/seed-database-data.md +++ b/docs/database/seed-database-data.md @@ -198,7 +198,6 @@ if (app.Environment.IsDevelopment()) ### [MySQL](#tab/mysql) -```csharp ```csharp builder.AddMySqlDbContext("TicketsDB", configureDbContextOptions: options => { From 8e2d6a4b212e595728ed9ca61c4094690349261a Mon Sep 17 00:00:00 2001 From: David Pine Date: Wed, 1 Oct 2025 08:22:14 -0500 Subject: [PATCH 3/3] Update docs/database/seed-database-data.md --- docs/database/seed-database-data.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/database/seed-database-data.md b/docs/database/seed-database-data.md index c151eac786..8ba5fdf81c 100644 --- a/docs/database/seed-database-data.md +++ b/docs/database/seed-database-data.md @@ -243,6 +243,9 @@ if (app.Environment.IsDevelopment()) --- +> [!NOTE] +> `UseSeeding` is called from the `EnsureCreated` method, and `UseAsyncSeeding` is called from the `EnsureCreatedAsync` method. When using this feature, it's recommended to implement both `UseSeeding` and `UseAsyncSeeding` methods using similar logic, even if the code using EF is asynchronous. EF Core tooling currently relies on the synchronous version of the method and will not seed the database correctly if the `UseSeeding` method isn't implemented. + The `UseSeeding` and `UseAsyncSeeding` methods provide several advantages over manual seeding approaches: - **Integrated lifecycle**: Seeding is automatically triggered when the database is created or when migrations are applied.