From 61c8587a8059088dd7c5a600afe6e3729abb7d97 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 5 Aug 2026 17:56:05 -0700 Subject: [PATCH 1/3] Expand and clarify migrations guidance. Fixes #736 Fixes #1043 Fixes #2952 Fixes #3258 Fixes #3425 Fixes #3426 Fixes #3444 Fixes #933 Fixes #3835 Fixes #5323 Fixes #4954 Fixes #4914 Fixes #4913 Fixes #4924 Fixes #4768 Fixes #4703 Fixes #4650 Fixes #4332 Fixes #4318 Fixes #4304 Fixes #4266 Fixes #4230 Fixes #3724 Fixes #3560 Fixes #3061 Fixes #3418 Fixes #2355 Fixes #2270 Fixes #2147 Fixes #1879 Fixes #1342 Fixes #807 Fixes #691 Fixes #564 --- .../core/cli/dbcontext-creation.md | 14 +- entity-framework/core/cli/dotnet.md | 9 +- entity-framework/core/cli/powershell.md | 7 +- .../managing-schemas/migrations/applying.md | 108 +++++++++++++-- .../core/managing-schemas/migrations/index.md | 7 +- .../managing-schemas/migrations/managing.md | 131 +++++++++++++----- .../managing-schemas/migrations/operations.md | 8 +- .../managing-schemas/migrations/projects.md | 119 ++++++++++++---- .../core/managing-schemas/migrations/teams.md | 25 +++- .../core/modeling/data-seeding.md | 26 +++- samples/core/Samples.sln | 7 + samples/core/Schemas/MigrationBundle/Blog.cs | 10 ++ .../MigrationBundle/BloggingContext.cs | 41 ++++++ .../MigrationBundle/BloggingContextFactory.cs | 17 +++ .../MigrationBundle/MigrationBundle.csproj | 18 +++ .../20260805232705_InitialCreate.Designer.cs | 39 ++++++ .../20260805232705_InitialCreate.cs | 34 +++++ .../20260805232730_AddIsActive.Designer.cs | 42 ++++++ .../Migrations/20260805232730_AddIsActive.cs | 29 ++++ .../BloggingContextModelSnapshot.cs | 39 ++++++ .../core/Schemas/MigrationBundle/Program.cs | 13 ++ .../Schemas/Migrations/CustomOperation.cs | 3 +- .../core/Schemas/Migrations/DataOperations.cs | 129 +++++++++++++++++ .../WebApplication1.Data.csproj | 2 +- .../ApplicationDbContextFactory.cs | 25 ++++ .../WebApplication1.Migrations.csproj | 8 ++ .../WebApplication1/WebApplication1.csproj | 6 +- 27 files changed, 821 insertions(+), 95 deletions(-) create mode 100644 samples/core/Schemas/MigrationBundle/Blog.cs create mode 100644 samples/core/Schemas/MigrationBundle/BloggingContext.cs create mode 100644 samples/core/Schemas/MigrationBundle/BloggingContextFactory.cs create mode 100644 samples/core/Schemas/MigrationBundle/MigrationBundle.csproj create mode 100644 samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.Designer.cs create mode 100644 samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.cs create mode 100644 samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.Designer.cs create mode 100644 samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.cs create mode 100644 samples/core/Schemas/MigrationBundle/Migrations/BloggingContextModelSnapshot.cs create mode 100644 samples/core/Schemas/MigrationBundle/Program.cs create mode 100644 samples/core/Schemas/Migrations/DataOperations.cs create mode 100644 samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/ApplicationDbContextFactory.cs diff --git a/entity-framework/core/cli/dbcontext-creation.md b/entity-framework/core/cli/dbcontext-creation.md index ff70bff66a..aa64bc9fbb 100644 --- a/entity-framework/core/cli/dbcontext-creation.md +++ b/entity-framework/core/cli/dbcontext-creation.md @@ -2,14 +2,14 @@ title: Design-time DbContext Creation - EF Core description: Strategies for creating a design-time DbContext with Entity Framework Core author: SamMonoRT -ms.date: 10/27/2020 +ms.date: 08/05/2026 uid: core/cli/dbcontext-creation --- # Design-time DbContext Creation Some of the EF Core Tools commands (for example, the [Migrations][1] commands) require a derived `DbContext` instance to be created at design time in order to gather details about the application's entity types and how they map to a database schema. In most cases, it is desirable that the `DbContext` thereby created is configured in a similar way to how it would be [configured at run time][2]. -There are various ways the tools try to create the `DbContext`: +There are various ways the tools try to create the `DbContext`. If an [`IDesignTimeDbContextFactory`](#from-a-design-time-factory) is found, the tools use it instead of the other creation patterns. A design-time factory is the recommended pattern for a [separate migrations project](xref:core/managing-schemas/migrations/projects) and for applications whose startup project is platform-specific. ## From application services @@ -34,7 +34,9 @@ You can also tell the tools how to create your DbContext by implementing the `](xref:core/cli/dbcontext-creation#from-a-design-time-factory), and use that project as both the target and startup project. See [Using a Separate Migrations Project](xref:core/managing-schemas/migrations/projects#platform-specific-applications). > [!IMPORTANT] > Xamarin.Android, Xamarin.iOS, Xamarin.Mac are now integrated directly into .NET (starting with .NET 6) as .NET for Android, .NET for iOS, and .NET for macOS. If you're building with these project types today, they should be upgraded to .NET SDK-style projects for continued support. For more information about upgrading Xamarin projects to .NET, see the [Upgrade from Xamarin to .NET & .NET MAUI](/dotnet/maui/migration) documentation. -Why is a dummy project required? As mentioned earlier, the tools have to execute application code at design time. To do that, they need to use the .NET runtime. When the EF Core model is in a project that targets .NET or .NET Framework, the EF Core tools borrow the runtime from the project. They can't do that if the EF Core model is in a .NET Standard class library. The .NET Standard is not an actual .NET implementation; it's a specification of a set of APIs that .NET implementations must support. Therefore .NET Standard is not sufficient for the EF Core tools to execute application code. The dummy project you create to use as startup project provides a concrete target platform into which the tools can load the .NET Standard class library. +The process running the tools must be able to load the target and startup assemblies. For example, a 64-bit tool process can't load an x86-only startup assembly. Prefer an AnyCPU migrations project. If design-time dependencies require a specific architecture, invoke a matching .NET SDK explicitly. The `--runtime` option controls restore for a runtime identifier; it does not change the architecture of the current tool process. ### ASP.NET Core environment @@ -159,6 +159,9 @@ Options: The [common options](#common-options) are listed above. +> [!WARNING] +> The migration argument specifies the state the database should be in after the command completes. If the database is currently at a newer migration, the command reverts every migration newer than the target by executing its `Down` operations. It doesn't apply one older migration out of order. + The following examples update the database to a specified migration. The first uses the migration name and the second uses the migration ID and a specified connection: ```dotnetcli @@ -367,6 +370,8 @@ The [common options](#common-options) are listed above. Generates a SQL script from migrations. +If `--output` isn't specified, the command writes the script to standard output. + Arguments: | Argument | Description | diff --git a/entity-framework/core/cli/powershell.md b/entity-framework/core/cli/powershell.md index 307c71e0a3..86ac055ab4 100644 --- a/entity-framework/core/cli/powershell.md +++ b/entity-framework/core/cli/powershell.md @@ -81,12 +81,12 @@ It's also possible to [put migrations code in a class library separate from the ### Other target frameworks -The Package Manager Console tools work with .NET or .NET Framework projects. Apps that have the EF Core model in a .NET Standard class library might not have a .NET or .NET Framework project. For example, this is true of Xamarin and Universal Windows Platform apps. In such cases, you can create a .NET or .NET Framework console app project whose only purpose is to act as startup project for the tools. The project can be a dummy project with no real code — it is only needed to provide a target for the tooling. +The Package Manager Console tools must execute application code using a .NET runtime. Don't use a platform-specific application, such as .NET MAUI, WinUI, Blazor WebAssembly, or Azure Functions, as the startup project for the tools. Instead, put migrations in a normal cross-platform .NET project with an [`IDesignTimeDbContextFactory`](xref:core/cli/dbcontext-creation#from-a-design-time-factory), and use that project as both the target and startup project. See [Using a Separate Migrations Project](xref:core/managing-schemas/migrations/projects#platform-specific-applications). > [!IMPORTANT] > Xamarin.Android, Xamarin.iOS, Xamarin.Mac are now integrated directly into .NET (starting with .NET 6) as .NET for Android, .NET for iOS, and .NET for macOS. If you're building with these project types today, they should be upgraded to .NET SDK-style projects for continued support. For more information about upgrading Xamarin projects to .NET, see the [Upgrade from Xamarin to .NET & .NET MAUI](/dotnet/maui/migration) documentation. -Why is a dummy project required? As mentioned earlier, the tools have to execute application code at design time. To do that, they need to use the .NET or .NET Framework runtime. When the EF Core model is in a project that targets .NET or .NET Framework, the EF Core tools borrow the runtime from the project. They can't do that if the EF Core model is in a .NET Standard class library. The .NET Standard is not an actual .NET implementation; it's a specification of a set of APIs that .NET implementations must support. Therefore .NET Standard is not sufficient for the EF Core tools to execute application code. The dummy project you create to use as startup project provides a concrete target platform into which the tools can load the .NET Standard class library. +Visual Studio and Package Manager Console normally run as 64-bit processes and can't load an x86-only startup assembly. Prefer an AnyCPU migrations project. When all design-time dependencies must be x86, use the .NET CLI with an explicitly selected x86 SDK/tool host. ### ASP.NET Core environment @@ -322,6 +322,9 @@ Updates the database to the last migration or to a specified migration. The [common parameters](#common-parameters) are listed above. +> [!WARNING] +> `-Migration` specifies the state the database should be in after the command completes. If the database is currently at a newer migration, the command reverts every migration newer than the target by executing its `Down` operations. It doesn't apply one older migration out of order. + > [!TIP] > The `Migration` parameter supports tab-expansion. diff --git a/entity-framework/core/managing-schemas/migrations/applying.md b/entity-framework/core/managing-schemas/migrations/applying.md index d76afbd195..cccffd20ee 100644 --- a/entity-framework/core/managing-schemas/migrations/applying.md +++ b/entity-framework/core/managing-schemas/migrations/applying.md @@ -2,7 +2,7 @@ title: Applying Migrations - EF Core description: Strategies for applying schema migrations to production and development databases using Entity Framework Core author: SamMonoRT -ms.date: 04/16/2026 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/applying ms.custom: sfi-ropc-nochange --- @@ -13,9 +13,26 @@ Once your migrations have been added, they need to be deployed and applied to yo > [!NOTE] > Whatever your deployment strategy, always inspect the generated migrations and test them before applying to a production database. A migration may drop a column when the intent was to rename it, or may fail for various reasons when applied to a database. +## Choose a deployment strategy + +For automated deployment, use a [migration bundle](#bundles). A bundle is a deployment artifact that can be generated in CI and executed later without the .NET SDK, the EF Core tools, or the application's source code. Use a [SQL script](#sql-scripts) instead when the SQL must be reviewed, modified, archived, or handed to a DBA before it is applied. + +For local development, `dotnet ef database update` or `Update-Database` is usually the simplest option. Aspire projects should use the [Aspire EF Core migrations integration](https://aspire.dev/integrations/databases/efcore/migrations/) to coordinate local migration execution and to publish bundles or scripts. + +| Strategy | Recommended use | Review SQL before execution | Requires SDK and source at execution | Uses EF migration locking | Runs EF seeding delegates | +| --- | --- | :---: | :---: | :---: | :---: | +| [SQL script](#sql-scripts) | DBA-controlled or review-gated deployment | Yes | No | No | No | +| [Migration bundle](#bundles) | Automated deployment | No | No | Yes | Yes | +| [EF command-line tools](#command-line-tools) | Local development and testing | No | Yes | Yes | Yes | +| [Runtime migration](#apply-migrations-at-runtime) | Applications that accept startup migration tradeoffs | No | No | Yes | Yes | + +EF Core 9 and later use migration locking. Synchronous operations and tooling invoke `UseSeeding`; asynchronous operations invoke `UseAsyncSeeding`. + +Use a separate identity for deployment that has permission to change the schema. The identity used by the application at run time should normally have only the permissions the application needs to read and write data. + ## SQL scripts -The recommended way to deploy migrations to a production database is by generating SQL scripts. The advantages of this strategy include the following: +SQL scripts are recommended when the deployment process requires the generated SQL to be inspected or changed before execution. The advantages of this strategy include the following: * SQL scripts can be reviewed for accuracy; this is important since applying schema changes to production databases is a potentially dangerous operation that could involve data loss. * In some cases, the scripts can be tuned to fit the specific needs of a production database. @@ -32,6 +49,12 @@ The following generates a SQL script from a blank database to the latest migrati dotnet ef migrations script ``` +By default, the command writes the script to standard output. Use `--output` (or `-o`) to create a deployment artifact with a predictable name: + +```dotnetcli +dotnet ef migrations script --idempotent --output artifacts/migrations.sql +``` + #### With From (to implied) The following generates a SQL script from the given migration to the latest migration. @@ -63,6 +86,12 @@ The following generates a SQL script from a blank database to the latest migrati Script-Migration ``` +Use `-Output` to write the script to a specific file: + +```powershell +Script-Migration -Idempotent -Output artifacts\migrations.sql +``` + #### With From (to implied) The following generates a SQL script from the given migration to the latest migration. @@ -91,10 +120,14 @@ Script generation accepts the following two arguments to indicate which range of * The **from** migration should be the last migration applied to the database before running the script. If no migrations have been applied, specify `0` (this is the default). * The **to** migration is the last migration that will be applied to the database after running the script. This defaults to the last migration in your project. +Migration scripts update an existing database. Provision the database itself through your infrastructure deployment or database administration process before applying the script. Database creation typically requires a different connection, elevated permissions, and provider-specific configuration. + ## Idempotent SQL scripts The SQL scripts generated above can only be applied to change your schema from one migration to another; it is your responsibility to apply the script appropriately, and only to databases in the correct migration state. EF Core also supports generating **idempotent** scripts, which internally check which migrations have already been applied (via the migrations history table), and only apply missing ones. This is useful if you don't exactly know what the last migration applied to the database was, or if you are deploying to multiple databases that may each be at a different migration. +Idempotent script support depends on the database provider. For example, SQLite doesn't currently support generating idempotent migration scripts. + The following generates idempotent migrations: ### [.NET CLI](#tab/dotnet-core-cli) @@ -160,6 +193,36 @@ Note that this can be used to roll back to an earlier migration as well. For more information on applying migrations via the command-line tools, see the [EF Core tools reference](xref:core/cli/index). +## Environment and configuration + +The tools execute application code to construct the `DbContext`. Provider selection, connection strings, and model configuration can therefore depend on the application environment. EF Core design-time tooling uses the `Development` environment when neither `ASPNETCORE_ENVIRONMENT` nor `DOTNET_ENVIRONMENT` is set. + +Set the environment explicitly when generating a deployment artifact and when executing a bundle. For example, in PowerShell: + +```powershell +$env:ASPNETCORE_ENVIRONMENT = 'Production' +dotnet ef migrations bundle --output artifacts\efbundle.exe +``` + +```powershell +$env:ASPNETCORE_ENVIRONMENT = 'Production' +.\efbundle.exe --connection $env:DEPLOYMENT_CONNECTION_STRING +``` + +Or in a POSIX-compatible shell: + +```bash +ASPNETCORE_ENVIRONMENT=Production \ + dotnet ef migrations bundle --output artifacts/efbundle + +ASPNETCORE_ENVIRONMENT=Production \ + ./efbundle --connection "$DEPLOYMENT_CONNECTION_STRING" +``` + +This also prevents a bundle from loading development user secrets unexpectedly. A safer default environment for bundles is tracked by [dotnet/efcore#36188](https://github.com/dotnet/efcore/issues/36188). Environment selection in the Visual Studio publish experience is tracked by [dotnet/efcore#11950](https://github.com/dotnet/efcore/issues/11950). + +Don't store production connection strings in source control or embed them in the bundle. Supply the deployment connection from the deployment system's secret store. The deployment identity should have schema permissions; the normal application identity usually shouldn't. + ## Bundles Migration bundles are single-file executables that can be used to apply migrations to a database. They address some of the shortcomings of the SQL script and command-line tools: @@ -168,19 +231,22 @@ Migration bundles are single-file executables that can be used to apply migratio * The transaction handling and continue-on-error behavior of these tools are inconsistent and sometimes unexpected. This can leave your database in an undefined state if a failure occurs when applying migrations. * Bundles can be generated as part of your CI process and easily executed later as part of your deployment process. * Bundles can be executed without installing the .NET SDK or EF Tool (or even the .NET Runtime, when self-contained), and they don't require the project's source code. +* Bundles use EF Core's migration locking and run configured `UseSeeding` logic. + +Unlike a SQL script, a bundle does not currently provide a way to inspect the SQL it will execute or list the migrations it contains. If your deployment requires SQL review, generate a script instead. Bundle inspection improvements are tracked by [dotnet/efcore#25872](https://github.com/dotnet/efcore/issues/25872). ### [.NET CLI](#tab/dotnet-core-cli) The following generates a bundle: ```dotnetcli -dotnet ef migrations bundle +dotnet ef migrations bundle --output artifacts/efbundle ``` The following generates a self-contained bundle for Linux: ```dotnetcli -dotnet ef migrations bundle --self-contained -r linux-x64 +dotnet ef migrations bundle --self-contained --target-runtime linux-x64 --output artifacts/efbundle ``` ### [Visual Studio](#tab/vs) @@ -188,13 +254,13 @@ dotnet ef migrations bundle --self-contained -r linux-x64 The following generates a bundle: ```powershell -Bundle-Migration +Bundle-Migration -Output artifacts\efbundle.exe ``` The following generates a self-contained bundle for Linux: -```dotnetcli -Bundle-Migration -SelfContained -TargetRuntime linux-x64 +```powershell +Bundle-Migration -SelfContained -TargetRuntime linux-x64 -Output artifacts\efbundle ``` *** @@ -226,8 +292,28 @@ The following example applies migrations to a local SQL Server instance using th .\efbundle.exe --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;User ID=myUsername;Password={;'$Credential;'here'}' ``` +To roll the database back, pass the migration that should remain applied. Passing `0` reverts all migrations: + +```powershell +.\efbundle.exe PreviousMigration --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;Integrated Security=True' +.\efbundle.exe 0 --connection 'Data Source=(local)\MSSQLSERVER;Initial Catalog=Blogging;Integrated Security=True' +``` + +> [!WARNING] +> A rollback executes the `Down` operations of every migration newer than the target and may result in data loss. Review and test rollback behavior before using it on production data. +> +> Configured seeding code runs after a downgrade. It must tolerate the schema of the target migration, including a missing application schema when the target is `0`. + > [!WARNING] -> Don't forget to copy appsettings.json alongside your bundle. The bundle relies on the presence of appsettings.json in the execution directory. +> If context configuration reads `appsettings.json`, copy the required settings files alongside the bundle. Configuration files are resolved from the bundle's execution directory. Don't put production secrets in these files; supply them through a secure configuration source or the `--connection` option. + +### Containers and deployment jobs + +Generate the bundle during the build and run it as a one-shot deployment job after the database is healthy. Don't install the SDK or run `dotnet ef` in the application image, and don't make every application replica run migrations from its entrypoint. Configure the deployment platform not to restart the migration container after it exits successfully. + +For Aspire applications, `AddEFMigrations` can coordinate migrations during local development. During publishing, `PublishAsMigrationBundle` can emit a bundle or a container image, and `PublishAsMigrationScript` can emit a SQL script. See [Apply EF Core migrations in Aspire](https://aspire.dev/integrations/databases/efcore/migrations/) for one-shot job configuration for Azure Container Apps, Docker Compose, and Kubernetes. + +The [migration bundle sample](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Schemas/MigrationBundle) demonstrates two SQLite migrations, idempotent seeding, forward application, and rollback-safe seeding. ### Migration bundle example @@ -310,7 +396,9 @@ PS C:\local\AllTogetherNow\SixOh> ## Apply migrations at runtime -It's possible for the application itself to apply migrations programmatically, typically during startup. While productive for local development and testing of migrations, this approach is inappropriate for managing production databases, for the following reasons: +It's possible for the application itself to apply migrations programmatically, typically during startup. EF Core 9 and later protect migration execution with a database-wide lock, so this can be acceptable for applications that prefer simple deployment and can tolerate startup migration behavior. A separate migration deployment step is still preferred when review, least-privilege credentials, coordinated rollout, or high availability is important. + +Consider the following tradeoffs: * For versions of EF prior to 9, if multiple instances of your application are running, both applications could attempt to apply the migration concurrently and fail (or worse, cause data corruption). * Similarly, if an application is accessing the database while another application migrates it, this can cause severe issues. @@ -339,7 +427,7 @@ Note that `MigrateAsync()` builds on top of the `IMigrator` service, which can b > [!WARNING] > -> * Carefully consider before using this approach in production. Experience has shown that the simplicity of this deployment strategy is outweighed by the issues it creates. Consider generating SQL scripts from migrations instead. +> * Carefully consider before using this approach in production. Prefer a migration bundle for automation or a SQL script when review and approval are required. > * Don't call `EnsureCreatedAsync()` before `MigrateAsync()`. `EnsureCreatedAsync()` bypasses Migrations to create the schema, which causes `MigrateAsync()` to fail. ## Migration locking diff --git a/entity-framework/core/managing-schemas/migrations/index.md b/entity-framework/core/managing-schemas/migrations/index.md index 7f8cbde524..1b59796a10 100644 --- a/entity-framework/core/managing-schemas/migrations/index.md +++ b/entity-framework/core/managing-schemas/migrations/index.md @@ -2,7 +2,7 @@ title: Migrations Overview - EF Core description: Overview of using migrations to manage database schemas with Entity Framework Core author: SamMonoRT -ms.date: 10/28/2020 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/index --- # Migrations Overview @@ -16,6 +16,9 @@ At a high level, migrations function in the following way: The rest of this page is a step-by-step beginner's guide for using migrations. Consult the other pages in this section for more in-depth information. +> [!TIP] +> Use the EF tools directly for local development. For platform-specific applications, put migrations in a [separate project with a design-time factory](xref:core/managing-schemas/migrations/projects). For deployment automation, generate a [migration bundle](xref:core/managing-schemas/migrations/applying#bundles); use a reviewed [SQL script](xref:core/managing-schemas/migrations/applying#sql-scripts) when SQL approval or modification is required. Aspire applications should use the [Aspire EF Core migrations integration](https://aspire.dev/integrations/databases/efcore/migrations/). + ## Getting started Let's assume you've just completed your first EF Core application, which contains the following simple model: @@ -140,7 +143,7 @@ Sometimes you may want to reference types from another DbContext. This can lead ### Next steps -The above was only a brief introduction to migrations. Please consult the other documentation pages to learn more about [managing migrations](xref:core/managing-schemas/migrations/managing), [applying them](xref:core/managing-schemas/migrations/applying), and other aspects. The [.NET CLI tool reference](xref:core/cli/index) also contains useful information on the different commands +The above was only a brief introduction to migrations. Learn more about [managing migrations](xref:core/managing-schemas/migrations/managing), [choosing a deployment strategy](xref:core/managing-schemas/migrations/applying#choose-a-deployment-strategy), and [using a separate migrations project](xref:core/managing-schemas/migrations/projects). The [.NET CLI tool reference](xref:core/cli/index) contains the complete command options. ## Additional resources diff --git a/entity-framework/core/managing-schemas/migrations/managing.md b/entity-framework/core/managing-schemas/migrations/managing.md index c8bfa59cb1..e6f248a20b 100644 --- a/entity-framework/core/managing-schemas/migrations/managing.md +++ b/entity-framework/core/managing-schemas/migrations/managing.md @@ -2,7 +2,7 @@ title: Managing Migrations - EF Core description: Adding, removing and otherwise managing database schema migrations with Entity Framework Core author: SamMonoRT -ms.date: 10/27/2020 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/managing ms.custom: sfi-ropc-nochange --- @@ -127,47 +127,56 @@ migrationBuilder.RenameColumn( > [!TIP] > The migration scaffolding process warns when an operation might result in data loss (like dropping a column). If you see that warning, be especially sure to review the migrations code for accuracy. -### Adding raw SQL +### Data operations -While renaming a column can be achieved via a built-in API, in many cases that is not possible. For example, we may want to replace existing `FirstName` and `LastName` properties with a single, new `FullName` property. The migration generated by EF Core will be the following: +Migrations can move data as well as change the schema. Choose the operation based on whether the values are known when the migration is written: -```csharp -migrationBuilder.DropColumn( - name: "FirstName", - table: "Customer"); +* Use `InsertData`, `UpdateData`, and `DeleteData` for fixed values and rows identified by explicit keys. EF Core translates these operations into provider-specific SQL, so they also work when generating scripts and bundles. +* Use `Sql` when the new values must be calculated from existing database data. SQL syntax can differ by provider; branch on `MigrationBuilder.ActiveProvider` when necessary. +* Define a [custom migration operation](xref:core/managing-schemas/migrations/operations) when a reusable operation needs provider-specific SQL generation. -migrationBuilder.DropColumn( - name: "LastName", - table: "Customer"); +Don't use the current `DbContext` or entity CLR types to move data in a migration. Historical migrations must continue to compile and behave the same after those types are changed or removed. -migrationBuilder.AddColumn( - name: "FullName", - table: "Customer", - nullable: true); -``` +#### Transform existing data -As before, this would cause unwanted data loss. To transfer the data from the old columns, we rearrange the migrations and introduce a raw SQL operation as follows: +When replacing columns, preserve the source data until the destination has been populated: -```csharp -migrationBuilder.AddColumn( - name: "FullName", - table: "Customer", - nullable: true); +1. Add the destination column as nullable. +2. Populate it from the existing columns. +3. Make the destination column required, if appropriate. +4. Drop the source columns. -migrationBuilder.Sql( -@" - UPDATE Customer - SET FullName = FirstName + ' ' + LastName; -"); +The following migration implements that sequence for SQL Server and SQLite: -migrationBuilder.DropColumn( - name: "FirstName", - table: "Customer"); +[!code-csharp[](../../../../samples/core/Schemas/Migrations/DataOperations.cs#snippet_RawSqlDataMigration)] -migrationBuilder.DropColumn( - name: "LastName", - table: "Customer"); -``` +Add a branch for every provider the application supports. Throwing for an unknown provider is safer than silently applying an incomplete migration. Don't build SQL from untrusted values; migration SQL is executed with schema-changing privileges. + +Some transformations cannot be reversed without losing information. Implement `Down` only when the original values can be reconstructed safely. Otherwise, fail explicitly and require restoring the data from a backup as part of the rollback procedure. + +#### Insert fixed data + +Use `InsertData` when the keys and values are known when the migration is written: + +[!code-csharp[](../../../../samples/core/Schemas/Migrations/DataOperations.cs#snippet_InsertData)] + +The corresponding `Down` method should call `DeleteData` with the same keys. + +#### Update fixed data + +`UpdateData` identifies a row by its key and sets one or more columns to fixed values: + +[!code-csharp[](../../../../samples/core/Schemas/Migrations/DataOperations.cs#snippet_UpdateData)] + +The `Down` method should restore the previous values. + +#### Delete fixed data + +`DeleteData` also identifies rows by key: + +[!code-csharp[](../../../../samples/core/Schemas/Migrations/DataOperations.cs#snippet_DeleteData)] + +If the delete must be reversible, the `Down` method should use `InsertData` to restore every deleted value. These operations don't query the current database state; use `Sql` or initialization-time seeding when behavior depends on existing data. ### Arbitrary changes via raw SQL @@ -224,6 +233,48 @@ After removing the migration, you can make the additional model changes and add > [!WARNING] > Avoid removing any migrations which have already been applied to production databases. Doing so means you won't be able to revert those migrations from the databases, and may break the assumptions made by subsequent migrations. +### If the migration was applied locally + +For a disposable development database, first update the database to the previous migration, and then remove the migration from the project. Use `0` as the target when removing the first migration. + +#### [.NET CLI](#tab/dotnet-core-cli) + +```dotnetcli +dotnet ef database update PreviousMigration +dotnet ef migrations remove +``` + +Alternatively, `--force` performs both steps: + +```dotnetcli +dotnet ef migrations remove --force +``` + +#### [Visual Studio](#tab/vs) + +```powershell +Update-Database PreviousMigration +Remove-Migration +``` + +Alternatively, `-Force` performs both steps: + +```powershell +Remove-Migration -Force +``` + +*** + +### If the migration was applied to a shared database + +Don't delete a migration that has been applied to a shared, test, or production database. Usually, keep the migration in the project and add a new corrective migration. If a planned rollback is required, execute the rollback while the original migration code is still available, and coordinate the application and database deployment. + +### Remove an older unapplied migration + +The tools remove only the latest migration. Don't delete a migration from the middle of the sequence and hand-edit the model snapshot. If the migration and every migration after it are unpublished and unapplied, remove the later migrations in reverse order, remove the unwanted migration, and then scaffold the retained model changes again. + +If the migrations were created on different branches, follow the [diverged migration tree](xref:core/managing-schemas/migrations/teams#resolving-diverged-migration-trees) workflow instead. + ## Listing migrations You can list all existing migrations as follows: @@ -242,6 +293,16 @@ Get-Migration *** +You can also inspect migration state programmatically: + +```csharp +var allMigrations = context.Database.GetMigrations(); +var appliedMigrations = await context.Database.GetAppliedMigrationsAsync(); +var pendingMigrations = await context.Database.GetPendingMigrationsAsync(); +``` + +`GetPendingMigrationsAsync` compares migrations in the configured migrations assembly with the migrations recorded in the target database. It doesn't detect model changes that haven't been captured in a migration; use the pending model changes check below for that. + ## Checking for pending model changes > [!NOTE] @@ -262,7 +323,7 @@ You can also perform this check programmatically using `context.Database.HasPend In some extreme cases, it may be necessary to remove all migrations and start over. This can be easily done by deleting your **Migrations** folder and dropping your database; at that point you can create a new initial migration, which will contain your entire current schema. -It's also possible to reset all migrations and create a single one without losing your data. This is sometimes called "squashing", and involves some manual work: +It's also possible to reset all migrations and create a single one without losing your data. This is called **squashing migrations**, and involves some manual work. EF Core doesn't currently provide an automated squashing command; see [dotnet/efcore#2174](https://github.com/dotnet/efcore/issues/2174). 1. Back up your database, in case something goes wrong. 2. In your database, delete all rows from the migrations history table (e.g. `DELETE FROM [__EFMigrationsHistory]` on SQL Server). @@ -278,6 +339,8 @@ VALUES (N'', N''); > [!WARNING] > Any [custom migration code](#customize-migration-code) will be lost when the **Migrations** folder is deleted. Any customizations must be applied to the new initial migration manually in order to be preserved. +Before squashing, verify that every deployed database is at a known migration and back it up. New databases must be created from the new initial migration, while existing databases must have the replacement migration recorded without executing schema operations that have already been applied. Test both paths before deployment. + ## Additional resources * [Entity Framework Core tools reference - .NET CLI](xref:core/cli/dotnet) : Includes commands to update, drop, add, remove, and more. diff --git a/entity-framework/core/managing-schemas/migrations/operations.md b/entity-framework/core/managing-schemas/migrations/operations.md index 0e3495517c..421d9d6702 100644 --- a/entity-framework/core/managing-schemas/migrations/operations.md +++ b/entity-framework/core/managing-schemas/migrations/operations.md @@ -2,13 +2,19 @@ title: Custom Migrations Operations - EF Core description: Managing custom and raw SQL migrations for database schema management with Entity Framework Core author: SamMonoRT -ms.date: 10/27/2020 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/operations --- # Custom Migrations Operations The MigrationBuilder API allows you to perform many different kinds of operations during a migration, but it's far from exhaustive. However, the API is also extensible allowing you to define your own operations. There are two ways to extend the API: Using the `Sql()` method, or by defining custom `MigrationOperation` objects. +## Built-in data operations + +Before defining a custom operation, consider the built-in `InsertData`, `UpdateData`, and `DeleteData` operations. They generate provider-specific SQL for fixed values and rows identified by explicit keys. Use `MigrationBuilder.Sql()` for transformations that calculate values from existing database rows. + +See [Data operations](xref:core/managing-schemas/migrations/managing#data-operations) for complete, reversible samples of inserting, updating, deleting, and transforming data in migrations. + To illustrate, let's look at implementing an operation that creates a database user using each approach. In our migrations, we want to enable writing the following code: ```csharp diff --git a/entity-framework/core/managing-schemas/migrations/projects.md b/entity-framework/core/managing-schemas/migrations/projects.md index 657459747e..64532fca29 100644 --- a/entity-framework/core/managing-schemas/migrations/projects.md +++ b/entity-framework/core/managing-schemas/migrations/projects.md @@ -2,63 +2,134 @@ title: Using a Separate Migrations Project - EF Core description: Using a separate migration project for managing database schemas with Entity Framework Core author: SamMonoRT -ms.date: 11/06/2020 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/projects --- # Using a Separate Migrations Project -You may want to store your migrations in a different project than the one containing your `DbContext`. This is recommended if your project uses a platform-specific project type, such as WinUI, Xamarin, MAUI, Blazor, or Azure Functions, or if it targets a specific runtime identifier (RID). You can also use this strategy to maintain multiple sets of migrations, for example, one for development and another for release-to-release upgrades. +You can store migrations in a different project from the one containing your `DbContext`. This is recommended when the application project is platform-specific, such as WinUI, .NET MAUI, Blazor WebAssembly, or Azure Functions, or when it targets a specific runtime identifier (RID). It can also be used to maintain more than one set of migrations. > [!TIP] > You can view this article's [sample on GitHub](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Schemas/ThreeProjectMigrations). -## Steps +## Project layout -1. Create a new class library. +The sample uses three projects: -2. Add a reference to your DbContext project. +| Project | Responsibility | References | +| --- | --- | --- | +| `WebApplication1.Data` | Owns the `DbContext` and entity types | EF Core provider | +| `WebApplication1.Migrations` | Owns migrations, the model snapshot, and design-time context creation | Data project, EF Core provider, and `Microsoft.EntityFrameworkCore.Design` | +| `WebApplication1` | Runs the application | Data project and migrations project | -3. Move the migrations and model snapshot files to the class library. - > [!TIP] - > If you have no existing migrations, generate one in the project containing the DbContext then move it. - > This is important because if the migrations project does not contain an existing migration, the Add-Migration command will be unable to find the DbContext. +The application needs a reference to the migrations project when it discovers or applies migrations at run time, for example by calling `Migrate`. If migrations are applied only by a deployment artifact and the application never loads them, that reference isn't required. -4. Configure the migrations assembly: +## Configure the projects - [!code-csharp[](../../../../samples/core/Schemas/ThreeProjectMigrations/WebApplication1/Startup.cs#snippet_MigrationsAssembly)] +1. Create a class library for the migrations and add a reference to the project containing the `DbContext`. -5. Add a reference to your migrations project from the **startup** project. +2. Add the database provider and `Microsoft.EntityFrameworkCore.Design` to the migrations project. Mark the design package as a private development dependency: ```xml - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + ``` - If this causes a circular dependency, you can update the base output path of the **migrations** project instead: +3. Implement [`IDesignTimeDbContextFactory`](xref:core/cli/dbcontext-creation#from-a-design-time-factory) in the migrations project. The factory allows the tools to create the context without running the application project: + + [!code-csharp[](../../../../samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/ApplicationDbContextFactory.cs#snippet_DesignTimeFactory)] + + Keep design-time provider and model configuration consistent with the runtime configuration. The sample accepts an optional connection string argument and uses a local development connection when no argument is supplied. + +4. Configure the migrations assembly when registering the context at run time: + + [!code-csharp[](../../../../samples/core/Schemas/ThreeProjectMigrations/WebApplication1/Startup.cs#snippet_MigrationsAssembly)] + +5. If the application applies migrations or otherwise discovers them at run time, add a normal reference from the application to the migrations project: ```xml - - ..\WebApplication1\bin\ - + + + ``` -If you did everything correctly, you should be able to add new migrations to the project. + The data project must not reference the migrations project. That would create a circular dependency because the migrations project already references the data project. + +6. If migrations already exist, move all migration files and the model snapshot to the migrations project and update their namespaces. When there are no existing migrations, the design-time factory allows the initial migration to be created directly in the migrations project. + +## Use the tools + +Use the migrations project as both the [target project and startup project](xref:core/cli/dotnet#target-project-and-startup-project). The target project receives generated files, while the startup project is built and executed by the tools. In this layout, using the migrations project for both prevents the tools from executing application startup code. + +### [.NET CLI](#tab/dotnet-core-cli) -## [.NET CLI](#tab/dotnet-core-cli) +Run these commands from the solution directory: ```dotnetcli -dotnet ef migrations add NewMigration --project WebApplication1.Migrations +dotnet ef migrations add NewMigration \ + --project WebApplication1.Migrations \ + --startup-project WebApplication1.Migrations ``` -## [Visual Studio](#tab/vs) +The same project options apply to other commands: + +```dotnetcli +dotnet ef migrations list \ + --project WebApplication1.Migrations \ + --startup-project WebApplication1.Migrations + +dotnet ef migrations script --output artifacts/migrations.sql \ + --project WebApplication1.Migrations \ + --startup-project WebApplication1.Migrations + +dotnet ef migrations bundle --output artifacts/efbundle \ + --project WebApplication1.Migrations \ + --startup-project WebApplication1.Migrations +``` + +Starting with EF Core 11, repeated project options can be stored in [`.config/dotnet-ef.json`](xref:core/cli/dotnet#configuration-file). + +### [Visual Studio](#tab/vs) + +Use the migrations project for both `-Project` and `-StartupProject`: ```powershell -Add-Migration NewMigration -Project WebApplication1.Migrations +Add-Migration NewMigration ` + -Project WebApplication1.Migrations ` + -StartupProject WebApplication1.Migrations ``` +The same parameters can be passed to `Get-Migration`, `Script-Migration`, `Bundle-Migration`, and the other Package Manager Console commands. + *** -> [!TIP] -> If your application uses dependency injection, consider implementing in your migrations project. This allows the EF tools to create your `DbContext` without needing to run the startup project. For more information, see [From a design-time factory](xref:core/cli/dbcontext-creation#from-a-design-time-factory). +Build the migrations project before running commands with `--no-build`, or before another process consumes its output. A normal `dotnet ef` command builds the target and startup projects automatically. + +## Platform-specific applications + +Don't use a platform-specific application project as the startup project for EF tools. Mobile, browser, desktop, function, and RID-specific projects can require a workload or native host that `dotnet ef` can't execute. Starting with EF Core 11, the tools warn when a platform-specific startup project is used. + +Use the layout described above for .NET MAUI, WinUI, Blazor WebAssembly, Azure Functions, and similar applications: + +1. Put the context and entity types in a shared data project. +2. Put migrations and `IDesignTimeDbContextFactory` in a normal cross-platform .NET project. +3. Run the tools with the migrations project as the target and startup project. +4. Reference the migrations project from the application only if the application loads or applies migrations at run time. + +Direct tooling support for Xamarin and MAUI platform projects isn't planned; see [dotnet/efcore#7152](https://github.com/dotnet/efcore/issues/7152). Xamarin applications should first be [upgraded to .NET MAUI](/dotnet/maui/migration). + +### Process architecture + +The process running the tools must be able to load every design-time assembly. A 64-bit Visual Studio or .NET process can't load an x86-only startup assembly, and the same constraint applies to Arm64 and other architectures. Prefer an AnyCPU migrations project. If design-time dependencies require a specific architecture, invoke a matching .NET SDK explicitly. + +The design-time process architecture is separate from the deployment target. When creating a bundle, use `--target-runtime` or `-TargetRuntime` to generate an artifact for the deployment RID, such as `linux-arm64` or `osx-arm64`. diff --git a/entity-framework/core/managing-schemas/migrations/teams.md b/entity-framework/core/managing-schemas/migrations/teams.md index f11152ed8c..64795fc437 100644 --- a/entity-framework/core/managing-schemas/migrations/teams.md +++ b/entity-framework/core/managing-schemas/migrations/teams.md @@ -2,7 +2,7 @@ title: Migrations in Team Environments - EF Core description: Best practices for managing migrations and resolving conflicts in team environments with Entity Framework Core author: SamMonoRT -ms.date: 02/18/2026 +ms.date: 08/05/2026 uid: core/managing-schemas/migrations/teams --- # Migrations in Team Environments @@ -13,6 +13,8 @@ For example, imagine developer A and B both create work branches at the same tim As a result, it is highly recommended to coordinate in advance and to avoid working concurrently on migrations in multiple branches when possible. +Migrations form an ordered sequence. Each migration's designer metadata represents the model at that point in the sequence and is used when the migration is removed. Don't resolve parallel migrations by sorting or renaming their files: the later migration would still contain metadata that doesn't include the other branch's changes. + ## Detecting diverged migration trees > [!NOTE] @@ -22,13 +24,26 @@ Starting with EF 11, the model snapshot records the ID of the latest migration. To resolve this, follow the steps in [Resolving diverged migration trees](#resolving-diverged-migration-trees) below: abort the merge, remove your migration (keeping your model changes), merge your teammate's changes, and then re-add your migration. +EF Core 10 and earlier don't record the latest migration ID in the model snapshot, so a source control system may merge the snapshot without reporting this conflict. The migration trees are still diverged and must be resolved using the same workflow. + ## Resolving diverged migration trees If, when merging a branch, a diverged migration tree is detected, resolve it by re-creating your migration. Follow these steps: -1. Abort the merge and rollback to your working directory before the merge -2. Remove your migration (but keep your model changes) -3. Merge your teammate's changes into your working directory -4. Re-add your migration +1. Abort the merge and return to your working directory before the merge. +2. Remove your migration, but keep the model changes that produced it. Source control can be used to remove only the generated migration files and restore the pre-migration snapshot. +3. Merge your teammate's changes into your working directory. +4. Re-add your migration so it is based on the merged model snapshot. After doing this, your migration is cleanly based on top of any migrations that have been added in the other branch, and its context snapshot contains all previous changes. Your migration can now be safely shared with the rest of the team. + +Don't run `migrations remove` after parallel migrations have already been merged into an invalid sequence. The command restores the model represented by the preceding migration's designer metadata, which may not contain the other branch's changes. Use source control to return to a coherent pre-merge state, and then follow the steps above. + +## Revert migration changes in source control + +Reverting a source control commit doesn't change any database. Before removing migration code, choose one of these approaches: + +* If the migration hasn't been applied to a shared database, remove the migration and then revert the model changes. +* If the migration has been applied, migrate the database to an earlier migration while the migration code is still available, or deploy a new corrective migration. Keep application and database deployment compatible throughout the rollback. + +Don't remove migration source that is still recorded in a shared database. If the code was already reverted, check out or restore the commit containing the migration to generate and test the rollback, and then commit a coherent migration sequence. diff --git a/entity-framework/core/modeling/data-seeding.md b/entity-framework/core/modeling/data-seeding.md index 921156e381..aeba7b2a55 100644 --- a/entity-framework/core/modeling/data-seeding.md +++ b/entity-framework/core/modeling/data-seeding.md @@ -2,7 +2,7 @@ title: Data Seeding - EF Core description: Using data seeding to populate a database with an initial set of data using Entity Framework Core author: AndriySvyryd -ms.date: 10/10/2024 +ms.date: 08/05/2026 uid: core/modeling/data-seeding --- @@ -35,6 +35,24 @@ These methods can be set up in the [options configuration step](/ef/core/dbconte > [!NOTE] > is called from the method, and is called from the method. When using this feature, it is recommended to implement both and 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 method is not implemented. +### Deployment behavior + +`UseSeeding` and `UseAsyncSeeding` run only when EF Core performs a database initialization or migration operation. Choose a deployment mechanism accordingly: + +| Operation | Seeding delegate invoked | +| --- | --- | +| `EnsureCreated` or `Migrate` | `UseSeeding` | +| `EnsureCreatedAsync` or `MigrateAsync` | `UseAsyncSeeding` | +| `dotnet ef database update` or `Update-Database` | `UseSeeding` | +| Migration bundle | `UseSeeding` | +| SQL script executed by an external SQL tool | None | + +For automated deployment that must run `UseSeeding`, use a [migration bundle](xref:core/managing-schemas/migrations/applying#bundles) or a dedicated initialization process. EF Core tools and bundles invoke the synchronous delegate, so always implement `UseSeeding` even if the application normally uses asynchronous APIs. Use a SQL script when review or DBA execution is required and seed data is represented by migration operations instead. See [Applying Migrations](xref:core/managing-schemas/migrations/applying#choose-a-deployment-strategy) for the tradeoffs. + +Seeding also runs after a migration downgrade. If the application supports downgrading to a migration that doesn't contain every table used by the seeding code, check that the required schema exists before querying it. This is especially important when reverting all migrations by targeting `0`. + +Aspire applications can coordinate local migration execution and publish migration bundles or scripts with the [Aspire EF Core migrations integration](https://aspire.dev/integrations/databases/efcore/migrations/). + ## Custom initialization logic @@ -82,8 +100,10 @@ See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tre Once the data has been added to the model, [migrations](xref:core/managing-schemas/migrations/index) should be used to apply the changes. +`HasData` changes are converted to `InsertData`, `UpdateData`, and `DeleteData` operations when a migration is scaffolded. Calling `Migrate` doesn't independently inspect the current `HasData` configuration. After changing model-managed data, add and deploy a new migration. + > [!TIP] -> If you need to apply migrations as part of an automated deployment you can [create a SQL script](xref:core/managing-schemas/migrations/applying#sql-scripts) that can be previewed before execution. +> For automated deployment, use a [migration bundle](xref:core/managing-schemas/migrations/applying#bundles). Use a [SQL script](xref:core/managing-schemas/migrations/applying#sql-scripts) when it must be previewed or changed before execution. Alternatively, you can use to create a new database containing the managed data, for example for a test database or when using the in-memory provider or any non-relational database. Note that if the database already exists, will neither update the schema nor managed data in the database. For relational databases you shouldn't call if you plan to use Migrations. @@ -116,3 +136,5 @@ If your scenario includes any of the following it is recommended to use are transformed to calls to `InsertData()`, `UpdateData()`, and `DeleteData()`. One way of working around some of the limitations of is to manually add these calls or [custom operations](xref:core/managing-schemas/migrations/operations) to the migration instead. [!code-csharp[CustomInsert](../../../samples/core/Modeling/DataSeeding/Migrations/20241016041555_Initial.cs?name=CustomInsert)] + +These operations are appropriate when the values and keys are fixed when the migration is written. They don't query the current database state. See [Data operations in migrations](xref:core/managing-schemas/migrations/managing#data-operations) for examples of `InsertData`, `UpdateData`, `DeleteData`, and provider-specific SQL transformations. diff --git a/samples/core/Samples.sln b/samples/core/Samples.sln index aca0882b06..0f5194a5e8 100644 --- a/samples/core/Samples.sln +++ b/samples/core/Samples.sln @@ -69,6 +69,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{0BFE EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migrations", "Schemas\Migrations\Migrations.csproj", "{D381F3EE-FEA4-4777-B9CA-7EE7E4C3289E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigrationBundle", "Schemas\MigrationBundle\MigrationBundle.csproj", "{DB841FB4-9038-462F-8FF2-A72D2BFD3F42}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TwoProjectMigrations", "TwoProjectMigrations", "{A717B3B8-DB2C-40A5-8C31-1DE1FA03CF40}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkerService1", "Schemas\TwoProjectMigrations\WorkerService1\WorkerService1.csproj", "{AC03251E-422C-4101-9EC8-D40A9A76AD64}" @@ -561,6 +563,10 @@ Global {114CFCD7-6755-4D74-BA81-E3C46D8FD4D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {114CFCD7-6755-4D74-BA81-E3C46D8FD4D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {114CFCD7-6755-4D74-BA81-E3C46D8FD4D8}.Release|Any CPU.Build.0 = Release|Any CPU + {DB841FB4-9038-462F-8FF2-A72D2BFD3F42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB841FB4-9038-462F-8FF2-A72D2BFD3F42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB841FB4-9038-462F-8FF2-A72D2BFD3F42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB841FB4-9038-462F-8FF2-A72D2BFD3F42}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -588,6 +594,7 @@ Global {0F8779BE-F32A-42A9-B2D7-1974A1D9DD09} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} {EE7AF867-1BC6-4C80-8856-B4DDB975E546} = {85AFD7F1-6943-40FE-B8EC-AA9DBB42CCA6} {D381F3EE-FEA4-4777-B9CA-7EE7E4C3289E} = {0BFEC418-1A37-4960-8488-EA8AFB916EB9} + {DB841FB4-9038-462F-8FF2-A72D2BFD3F42} = {0BFEC418-1A37-4960-8488-EA8AFB916EB9} {A717B3B8-DB2C-40A5-8C31-1DE1FA03CF40} = {0BFEC418-1A37-4960-8488-EA8AFB916EB9} {AC03251E-422C-4101-9EC8-D40A9A76AD64} = {A717B3B8-DB2C-40A5-8C31-1DE1FA03CF40} {00A36564-7244-484A-A8C4-C56D343E255D} = {A717B3B8-DB2C-40A5-8C31-1DE1FA03CF40} diff --git a/samples/core/Schemas/MigrationBundle/Blog.cs b/samples/core/Schemas/MigrationBundle/Blog.cs new file mode 100644 index 0000000000..0054aeb536 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Blog.cs @@ -0,0 +1,10 @@ +namespace MigrationBundle; + +public class Blog +{ + public int Id { get; set; } + + public required string Url { get; set; } + + public bool IsActive { get; set; } +} diff --git a/samples/core/Schemas/MigrationBundle/BloggingContext.cs b/samples/core/Schemas/MigrationBundle/BloggingContext.cs new file mode 100644 index 0000000000..48e76583e1 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/BloggingContext.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore; + +namespace MigrationBundle; + +public class BloggingContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Blogs => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseSeeding( + (context, _) => + { + if (BlogsTableExists(context) + && !context.Set().Any(b => b.Url == "https://example.com")) + { + context.Set().Add(new Blog { Url = "https://example.com" }); + context.SaveChanges(); + } + }) + .UseAsyncSeeding( + async (context, _, cancellationToken) => + { + if (await BlogsTableExistsAsync(context, cancellationToken) + && !await context.Set().AnyAsync(b => b.Url == "https://example.com", cancellationToken)) + { + context.Set().Add(new Blog { Url = "https://example.com" }); + await context.SaveChangesAsync(cancellationToken); + } + }); + + private static bool BlogsTableExists(DbContext context) + => context.Database + .SqlQueryRaw("SELECT COUNT(*) AS Value FROM sqlite_master WHERE type = 'table' AND name = 'Blogs'") + .Single() != 0; + + private static async Task BlogsTableExistsAsync(DbContext context, CancellationToken cancellationToken) + => await context.Database + .SqlQueryRaw("SELECT COUNT(*) AS Value FROM sqlite_master WHERE type = 'table' AND name = 'Blogs'") + .SingleAsync(cancellationToken) != 0; +} diff --git a/samples/core/Schemas/MigrationBundle/BloggingContextFactory.cs b/samples/core/Schemas/MigrationBundle/BloggingContextFactory.cs new file mode 100644 index 0000000000..0f25418880 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/BloggingContextFactory.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace MigrationBundle; + +public class BloggingContextFactory : IDesignTimeDbContextFactory +{ + public BloggingContext CreateDbContext(string[] args) + { + var connectionString = args.FirstOrDefault() ?? "Data Source=blogging.db"; + var options = new DbContextOptionsBuilder() + .UseSqlite(connectionString) + .Options; + + return new BloggingContext(options); + } +} diff --git a/samples/core/Schemas/MigrationBundle/MigrationBundle.csproj b/samples/core/Schemas/MigrationBundle/MigrationBundle.csproj new file mode 100644 index 0000000000..2394a11ca0 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/MigrationBundle.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.Designer.cs b/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.Designer.cs new file mode 100644 index 0000000000..df2cbabcb6 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.Designer.cs @@ -0,0 +1,39 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MigrationBundle; + +#nullable disable + +namespace MigrationBundle.Migrations +{ + [DbContext(typeof(BloggingContext))] + [Migration("20260805232705_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); + + modelBuilder.Entity("MigrationBundle.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.cs b/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.cs new file mode 100644 index 0000000000..a43c6ed62f --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Migrations/20260805232705_InitialCreate.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MigrationBundle.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Blogs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Url = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Blogs", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Blogs"); + } + } +} diff --git a/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.Designer.cs b/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.Designer.cs new file mode 100644 index 0000000000..6abd7d1039 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.Designer.cs @@ -0,0 +1,42 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MigrationBundle; + +#nullable disable + +namespace MigrationBundle.Migrations +{ + [DbContext(typeof(BloggingContext))] + [Migration("20260805232730_AddIsActive")] + partial class AddIsActive + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); + + modelBuilder.Entity("MigrationBundle.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.cs b/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.cs new file mode 100644 index 0000000000..964d77ba38 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Migrations/20260805232730_AddIsActive.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MigrationBundle.Migrations +{ + /// + public partial class AddIsActive : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsActive", + table: "Blogs", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsActive", + table: "Blogs"); + } + } +} diff --git a/samples/core/Schemas/MigrationBundle/Migrations/BloggingContextModelSnapshot.cs b/samples/core/Schemas/MigrationBundle/Migrations/BloggingContextModelSnapshot.cs new file mode 100644 index 0000000000..481e2f11aa --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Migrations/BloggingContextModelSnapshot.cs @@ -0,0 +1,39 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MigrationBundle; + +#nullable disable + +namespace MigrationBundle.Migrations +{ + [DbContext(typeof(BloggingContext))] + partial class BloggingContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); + + modelBuilder.Entity("MigrationBundle.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Blogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/core/Schemas/MigrationBundle/Program.cs b/samples/core/Schemas/MigrationBundle/Program.cs new file mode 100644 index 0000000000..06bbc52327 --- /dev/null +++ b/samples/core/Schemas/MigrationBundle/Program.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; +using MigrationBundle; + +if (args.Length == 2 + && args[0] == "verify") +{ + var options = new DbContextOptionsBuilder() + .UseSqlite(args[1]) + .Options; + + await using var context = new BloggingContext(options); + Console.WriteLine($"Blogs: {await context.Blogs.CountAsync()}"); +} diff --git a/samples/core/Schemas/Migrations/CustomOperation.cs b/samples/core/Schemas/Migrations/CustomOperation.cs index afa709dea2..af2abdf3cc 100644 --- a/samples/core/Schemas/Migrations/CustomOperation.cs +++ b/samples/core/Schemas/Migrations/CustomOperation.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; #region snippet_CreateUserOperation @@ -59,7 +60,7 @@ private void Generate( MigrationCommandListBuilder builder) { var sqlHelper = Dependencies.SqlGenerationHelper; - var stringMapping = Dependencies.TypeMappingSource.FindMapping(typeof(string)); + var stringMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string)); builder .Append("CREATE USER ") diff --git a/samples/core/Schemas/Migrations/DataOperations.cs b/samples/core/Schemas/Migrations/DataOperations.cs new file mode 100644 index 0000000000..06850dc53c --- /dev/null +++ b/samples/core/Schemas/Migrations/DataOperations.cs @@ -0,0 +1,129 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +internal class PopulateCustomerFullName : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + #region snippet_RawSqlDataMigration + migrationBuilder.AddColumn( + name: "FullName", + table: "Customers", + nullable: true); + + if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.SqlServer") + { + migrationBuilder.Sql( + """ + UPDATE [Customers] + SET [FullName] = [FirstName] + N' ' + [LastName]; + """); + } + else if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite") + { + migrationBuilder.Sql( + """ + UPDATE "Customers" + SET "FullName" = "FirstName" || ' ' || "LastName"; + """); + } + else + { + throw new NotSupportedException( + $"Data migration is not implemented for provider {migrationBuilder.ActiveProvider}."); + } + + migrationBuilder.AlterColumn( + name: "FullName", + table: "Customers", + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.DropColumn( + name: "FirstName", + table: "Customers"); + + migrationBuilder.DropColumn( + name: "LastName", + table: "Customers"); + #endregion + } + + protected override void Down(MigrationBuilder migrationBuilder) + => throw new NotSupportedException("Restore the original name columns from a backup before downgrading."); +} + +internal class InsertCountries : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + #region snippet_InsertData + migrationBuilder.InsertData( + table: "Countries", + columns: new[] { "CountryId", "Name" }, + values: new object[,] + { + { 1, "United States" }, + { 2, "Canada" } + }); + #endregion + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "Countries", + keyColumn: "CountryId", + keyValue: 1); + + migrationBuilder.DeleteData( + table: "Countries", + keyColumn: "CountryId", + keyValue: 2); + } +} + +internal class RenameCountry : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + #region snippet_UpdateData + migrationBuilder.UpdateData( + table: "Countries", + keyColumn: "CountryId", + keyValue: 1, + column: "Name", + value: "United States of America"); + #endregion + } + + protected override void Down(MigrationBuilder migrationBuilder) + => migrationBuilder.UpdateData( + table: "Countries", + keyColumn: "CountryId", + keyValue: 1, + column: "Name", + value: "United States"); +} + +internal class RemoveObsoleteCountry : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + #region snippet_DeleteData + migrationBuilder.DeleteData( + table: "Countries", + keyColumn: "CountryId", + keyValue: 2); + #endregion + } + + protected override void Down(MigrationBuilder migrationBuilder) + => migrationBuilder.InsertData( + table: "Countries", + columns: new[] { "CountryId", "Name" }, + values: new object[] { 2, "Canada" }); +} \ No newline at end of file diff --git a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Data/WebApplication1.Data.csproj b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Data/WebApplication1.Data.csproj index 1f26261919..151690714f 100644 --- a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Data/WebApplication1.Data.csproj +++ b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Data/WebApplication1.Data.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/ApplicationDbContextFactory.cs b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/ApplicationDbContextFactory.cs new file mode 100644 index 0000000000..cb32e8a3b6 --- /dev/null +++ b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/ApplicationDbContextFactory.cs @@ -0,0 +1,25 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using WebApplication1.Data; + +namespace WebApplication1.Migrations; + +#region snippet_DesignTimeFactory +public class ApplicationDbContextFactory : IDesignTimeDbContextFactory +{ + public ApplicationDbContext CreateDbContext(string[] args) + { + var connectionString = args.FirstOrDefault() + ?? @"Server=(localdb)\mssqllocaldb;Database=WebApplication1;Trusted_Connection=True"; + + var options = new DbContextOptionsBuilder() + .UseSqlServer( + connectionString, + sqlServer => sqlServer.MigrationsAssembly(typeof(ApplicationDbContextFactory).Assembly.GetName().Name)) + .Options; + + return new ApplicationDbContext(options); + } +} +#endregion diff --git a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/WebApplication1.Migrations.csproj b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/WebApplication1.Migrations.csproj index ab5016e6cc..5c75b6df04 100644 --- a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/WebApplication1.Migrations.csproj +++ b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1.Migrations/WebApplication1.Migrations.csproj @@ -4,6 +4,14 @@ net10.0 + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1/WebApplication1.csproj b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1/WebApplication1.csproj index f8937b3034..d7570211d0 100644 --- a/samples/core/Schemas/ThreeProjectMigrations/WebApplication1/WebApplication1.csproj +++ b/samples/core/Schemas/ThreeProjectMigrations/WebApplication1/WebApplication1.csproj @@ -13,11 +13,7 @@ - - - all - compile - + From e68277359161bee6c44dc18a2f147f9b74336850 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 6 Aug 2026 16:00:52 -0700 Subject: [PATCH 2/3] Fix note --- entity-framework/core/modeling/data-seeding.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/entity-framework/core/modeling/data-seeding.md b/entity-framework/core/modeling/data-seeding.md index aeba7b2a55..392d1096f0 100644 --- a/entity-framework/core/modeling/data-seeding.md +++ b/entity-framework/core/modeling/data-seeding.md @@ -33,7 +33,8 @@ These methods can be set up in the [options configuration step](/ef/core/dbconte [!code-csharp[ContextOptionSeeding](../../../samples/core/Modeling/DataSeeding/DataSeedingContext.cs?name=ContextOptionSeeding)] > [!NOTE] -> is called from the method, and is called from the method. When using this feature, it is recommended to implement both and 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 method is not implemented. +> / are invoked during / and after migrations are applied (for example, `Migrate`/`MigrateAsync`, `dotnet ef database update`, and migration bundles). +> EF Core tooling and bundles currently rely on the synchronous delegate, so always implement `UseSeeding` even if your application normally uses asynchronous APIs. ### Deployment behavior From 0d5ffaaef4b8ff00e31e79e5b84985f840ec83c5 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 6 Aug 2026 16:01:11 -0700 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- entity-framework/core/cli/dbcontext-creation.md | 4 ++-- entity-framework/core/managing-schemas/migrations/teams.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/entity-framework/core/cli/dbcontext-creation.md b/entity-framework/core/cli/dbcontext-creation.md index aa64bc9fbb..d8824184d4 100644 --- a/entity-framework/core/cli/dbcontext-creation.md +++ b/entity-framework/core/cli/dbcontext-creation.md @@ -64,6 +64,6 @@ Update-Database -Args '--environment Production' [2]: xref:core/dbcontext-configuration/index [3]: /aspnet/core/fundamentals/host/web-host [4]: /aspnet/core/fundamentals/host/generic-host - [5]: xref:core/dbcontext-configuration/index#dbcontext-in-dependency-injection-for-aspnet-core - [6]: xref:core/dbcontext-configuration/index#dbcontext-in-dependency-injection-for-aspnet-core + [5]: xref:core/dbcontext-configuration/index + [6]: xref:Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContext* [7]: xref:core/dbcontext-configuration/index#basic-dbcontext-initialization-with-new diff --git a/entity-framework/core/managing-schemas/migrations/teams.md b/entity-framework/core/managing-schemas/migrations/teams.md index 64795fc437..9154299fd0 100644 --- a/entity-framework/core/managing-schemas/migrations/teams.md +++ b/entity-framework/core/managing-schemas/migrations/teams.md @@ -37,7 +37,7 @@ If, when merging a branch, a diverged migration tree is detected, resolve it by After doing this, your migration is cleanly based on top of any migrations that have been added in the other branch, and its context snapshot contains all previous changes. Your migration can now be safely shared with the rest of the team. -Don't run `migrations remove` after parallel migrations have already been merged into an invalid sequence. The command restores the model represented by the preceding migration's designer metadata, which may not contain the other branch's changes. Use source control to return to a coherent pre-merge state, and then follow the steps above. +Don't run `dotnet ef migrations remove` (or `Remove-Migration`) after parallel migrations have already been merged into an invalid sequence. The command restores the model represented by the preceding migration's designer metadata, which may not contain the other branch's changes. Use source control to return to a coherent pre-merge state, and then follow the steps above. ## Revert migration changes in source control