title | description | ms.date | ms.topic |
---|---|---|---|
Apply EF Core migrations in .NET Aspire |
Learn about how to to apply Entity Framework Core migrations in .NET Aspire |
07/31/2024 |
how-to |
Since .NET Aspire projects use a containerized architecture, databases are ephemeral and can be recreated at any time. Entity Framework Core (EF Core) uses a feature called migrations to create and update database schemas. Since databases are recreated when the app starts, you need to apply migrations to initialize the database schema each time your app starts. This is accomplished by registering a migration service project in your app that runs migrations during startup.
In this tutorial, you learn how to configure .NET Aspire projects to run EF Core migrations during app startup.
[!INCLUDE aspire-prereqs]
This tutorial uses a sample app that demonstrates how to apply EF Core migrations in .NET Aspire. Use Visual Studio to clone the sample app from GitHub or use the following command:
git clone https://github.com/MicrosoftDocs/aspire-docs-samples/
The sample app is in the SupportTicketApi folder. Open the solution in Visual Studio or VS Code and take a moment to review the sample app and make sure it runs before proceeding. The sample app is a rudimentary support ticket API, and it contains the following projects:
- SupportTicketApi.Api: The ASP.NET Core project that hosts the API.
- SupportTicketApi.Data: Contains the EF Core contexts and models.
- SupportTicketApi.AppHost: Contains the .NET Aspire app host and configuration.
- SupportTicketApi.ServiceDefaults: Contains the default service configurations.
Run the app to ensure it works as expected. From the .NET Aspire dashboard, select the https Swagger endpoint and test the API's GET /api/SupportTickets endpoint by expanding the operation and selecting Try it out. Select Execute to send the request and view the response:
[
{
"id": 1,
"title": "Initial Ticket",
"description": "Test ticket, please ignore."
}
]
Start by creating some migrations to apply.
-
Open a terminal (Ctrl+` in Visual Studio).
-
Set :::no-loc text="SupportTicketApi\SupportTicketApi.Api"::: as the current directory.
-
Use the
dotnet ef
command-line tool to create a new migration to capture the initial state of the database schema:dotnet ef migrations add InitialCreate --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
The proceeding command:
- Runs EF Core migration command-line tool in the SupportTicketApi.Api directory.
dotnet ef
is run in this location because the API service is where the DB context is used. - Creates a migration named InitialCreate.
- Creates the migration in the in the Migrations folder in the SupportTicketApi.Data project.
- Runs EF Core migration command-line tool in the SupportTicketApi.Api directory.
-
Modify the model so that it includes a new property. Open :::no-loc text="SupportTicketApi.Data\Models\SupportTicket.cs"::: and add a new property to the
SupportTicket
class::::code source="~/aspire-docs-samples-solution/SupportTicketApi/SupportTicketApi.Data/Models/SupportTicket.cs" range="5-13" highlight="8" :::
-
Create another new migration to capture the changes to the model:
dotnet ef migrations add AddCompleted --project ..\SupportTicketApi.Data\SupportTicketApi.Data.csproj
Now you've got some migrations to apply. Next, you'll create a migration service that applies these migrations during app startup.
To run the migrations at startup, you need to create a service that applies the migrations.
-
Add a new Worker Service project to the solution. If using Visual Studio, right-click the solution in Solution Explorer and select :::no-loc text="Add"::: > :::no-loc text="New Project":::. Select :::no-loc text="Worker Service"::: and name the project :::no-loc text="SupportTicketApi.MigrationService":::. If using the command line, use the following commands from the solution directory:
dotnet new worker -n SupportTicketApi.MigrationService dotnet sln add SupportTicketApi.MigrationService
-
Add the :::no-loc text="SupportTicketApi.Data"::: and :::no-loc text="SupportTicketApi.ServiceDefaults"::: project references to the :::no-loc text="SupportTicketApi.MigrationService"::: project using Visual Studio or the command line:
dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.Data dotnet add SupportTicketApi.MigrationService reference SupportTicketApi.ServiceDefaults
-
Add the 📦 Aspire.Microsoft.EntityFrameworkCore.SqlServer NuGet package reference to the :::no-loc text="SupportTicketApi.MigrationService"::: project using Visual Studio or the command line:
dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
-
Add the highlighted lines to the :::no-loc text="Program.cs"::: file in the :::no-loc text="SupportTicketApi.MigrationService"::: project:
:::code source="~/aspire-docs-samples-solution/SupportTicketApi/SupportTicketApi.MigrationService/Program.cs" highlight="1,6,9-12" :::
In the preceding code:
- The
AddServiceDefaults
extension method adds service defaults functionality. - The
AddOpenTelemetry
extension method configures OpenTelemetry functionality. - The
AddSqlServerDbContext
extension method adds theTicketContext
service to the service collection. This service is used to run migrations and seed the database.
- The
-
Replace the contents of the :::no-loc text="Worker.cs"::: file in the :::no-loc text="SupportTicketApi.MigrationService"::: project with the following code:
:::code source="~/aspire-docs-samples-solution/SupportTicketApi/SupportTicketApi.MigrationService/Worker.cs" :::
In the preceding code:
- The
ExecuteAsync
method is called when the worker starts. It in turn performs the following steps:- Gets a reference to the
TicketContext
service from the service provider. - Calls
EnsureDatabaseAsync
to create the database if it doesn't exist. - Calls
RunMigrationAsync
to apply any pending migrations. - Calls
SeedDataAsync
to seed the database with initial data. - Stops the worker with
StopApplication
.
- Gets a reference to the
- The
EnsureDatabaseAsync
,RunMigrationAsync
, andSeedDataAsync
methods all encapsulate their respective database operations using execution strategies to handle transient errors that may occur when interacting with the database. To learn more about execution strategies, see Connection Resiliency.
- The
The migration service is created, but it needs to be added to the .NET Aspire app host so that it runs when the app starts.
-
In the :::no-loc text="SupportTicketApi.AppHost"::: project, open the :::no-loc text="Program.cs"::: file.
-
Add the following highlighted code to the
ConfigureServices
method::::code source="~/aspire-docs-samples-solution/SupportTicketApi/SupportTicketApi.AppHost/Program.cs" highlight="9-10" :::
This enlists the :::no-loc text="SupportTicketApi.MigrationService"::: project as a service in the .NET Aspire app host.
[!IMPORTANT] If you are using Visual Studio, and you selected the :::no-loc text="Enlist in Aspire orchestration"::: option when creating the Worker Service project, similar code is added automatically with the service name
supportticketapi-migrationservice
. Replace that code with the preceding code.
Since the migration service seeds the database, you should remove the existing data seeding code from the API project.
-
In the :::no-loc text="SupportTicketApi.Api"::: project, open the :::no-loc text="Program.cs"::: file.
-
Delete the highlighted lines.
:::code source="~/aspire-docs-samples-main/SupportTicketApi/SupportTicketApi.Api/Program.cs" range="20-36" highlight="6-16" :::
Now that the migration service is configured, run the app to test the migrations.
-
Run the app and observe the :::no-loc text="SupportTicketApi"::: dashboard.
-
After a short wait, the
migrations
service state will display Finished.:::image type="content" source="media/ef-core-migrations/dashboard-post-migration.png" lightbox="media/ef-core-migrations/dashboard-post-migration.png" alt-text="A screenshot of the .NET Aspire dashboard with the migration service in a Finished state." :::
-
Select the :::no-loc text="View"::: link on the migration service to investigate the logs showing the SQL commands that were executed.
You can find the completed sample app on GitHub.
The Aspire Shop sample app uses this approach to apply migrations. See the AspireShop.CatalogDbManager
project for the migration service implementation.