From 7ed4f3fc81de6775a31bee29f9649057d6637e90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:38:16 +0000 Subject: [PATCH 1/5] Initial plan From f16ba9a14bd038c3eeb820d3db8e372f6b611a99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:43:03 +0000 Subject: [PATCH 2/5] Document migration locking in applying.md and update cross-references Agent-Logs-Url: https://github.com/dotnet/EntityFramework.Docs/sessions/a96053e2-50e2-479a-8d0d-fb9bd8bd5c9d Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../managing-schemas/migrations/applying.md | 75 +++++++++++++++++++ .../core/modeling/data-seeding.md | 2 +- .../core/providers/sqlite/limitations.md | 12 ++- .../core/what-is-new/ef-core-9.0/whatsnew.md | 2 + 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/entity-framework/core/managing-schemas/migrations/applying.md b/entity-framework/core/managing-schemas/migrations/applying.md index b20af256d4..b3b87f0b8d 100644 --- a/entity-framework/core/managing-schemas/migrations/applying.md +++ b/entity-framework/core/managing-schemas/migrations/applying.md @@ -341,3 +341,78 @@ Note that `MigrateAsync()` builds on top of the `IMigrator` service, which can b > > * 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. > * Don't call `EnsureCreatedAsync()` before `MigrateAsync()`. `EnsureCreatedAsync()` bypasses Migrations to create the schema, which causes `MigrateAsync()` to fail. + +## Migration locking + +Starting with EF Core 9, and automatically acquire a database-wide lock before applying any migrations. This protects against database corruption that could result from multiple application instances running migrations concurrently, which is a common scenario when [applying migrations at runtime](#apply-migrations-at-runtime). The lock is held for the duration of the migration execution, including any [seeding code](xref:core/modeling/data-seeding#use-seeding-method), and is automatically released when the operation completes. + +Migration locking applies when migrations are applied using any of the following methods: + +* `dotnet ef database update` (.NET CLI) +* `Update-Database` (Package Manager Console) +* [Migration bundles](#bundles) +* and (runtime migration) + +[SQL scripts](#sql-scripts) are not affected by migration locking, since they are applied outside of EF Core. + +> [!NOTE] +> If you are using a third-party database provider, check the provider documentation to understand the locking behavior specific to your database. The information below covers the SQL Server and SQLite providers included with EF Core. + +### How the lock works + +The locking mechanism is provider-specific: + +* **SQL Server** uses `sp_getapplock` to acquire a session-level application lock named `__EFMigrationsLock`. This lock is automatically released when the database connection is closed, so it cannot become abandoned even if the application terminates unexpectedly. +* **SQLite** does not have a built-in application locking mechanism, so EF Core creates a `__EFMigrationsLock` table and uses it to coordinate access. A row is inserted to acquire the lock and deleted to release it. This lock requires explicit release (see [Handling abandoned locks](#handling-abandoned-locks)). + +If another migration execution is already in progress, the new request waits until the lock becomes available. + +### Handling abandoned locks + +In most cases, the migration lock is released automatically when the operation completes or when the connection is closed. However, on **SQLite**, if the application terminates unexpectedly (for example, the process is killed during migration), the lock row in the `__EFMigrationsLock` table may not be cleaned up. This prevents any subsequent migration from completing, because each attempt will wait indefinitely for the lock to be released. + +To resolve an abandoned lock on SQLite, delete the `__EFMigrationsLock` table from the database: + +```sql +DROP TABLE "__EFMigrationsLock"; +``` + +Or, alternatively, delete all rows from the table: + +```sql +DELETE FROM "__EFMigrationsLock"; +``` + +After clearing the lock, subsequent migration operations proceed normally. The table is automatically recreated as needed. + +> [!NOTE] +> This issue is specific to the SQLite provider. On SQL Server, the lock is session-based and is automatically released when the connection closes, so abandoned locks are not a concern. + +### Migrations and explicit transactions + +Starting with EF Core 9, calling inside an explicit transaction throws a warning by default. This is because wrapping migrations in an external transaction prevents the migration lock from being acquired, which leaves the database unprotected from concurrent migration applications. EF Core manages its own transactions internally as needed during migration. + +If you were previously wrapping `MigrateAsync()` in an explicit transaction: + +```csharp +// This will throw a warning in EF Core 9+ +await strategy.ExecuteAsync(async () => +{ + await using var transaction = await dbContext.Database.BeginTransactionAsync(); + await dbContext.Database.MigrateAsync(); + await transaction.CommitAsync(); +}); +``` + +Remove the external transaction and `ExecutionStrategy`: + +```csharp +// Recommended: let EF Core manage transactions +await dbContext.Database.MigrateAsync(); +``` + +If your scenario requires an explicit transaction and you have another mechanism in place to prevent concurrent migration application, you can suppress the warning: + +```csharp +options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning)) +``` diff --git a/entity-framework/core/modeling/data-seeding.md b/entity-framework/core/modeling/data-seeding.md index 218eb4400b..921156e381 100644 --- a/entity-framework/core/modeling/data-seeding.md +++ b/entity-framework/core/modeling/data-seeding.md @@ -21,7 +21,7 @@ There are several ways this can be accomplished in EF Core: ## Configuration options `UseSeeding` and `UseAsyncSeeding` methods -EF 9 introduced and methods, which provide a convenient way of seeding the database with initial data. These methods aim to improve the experience of using custom initialization logic (explained below). They provide one clear location where all the data seeding code can be placed. Moreover, the code inside and methods is protected by the [migration locking mechanism](/ef/core/what-is-new/ef-core-9.0/whatsnew#concurrent-migrations) to prevent concurrency issues. +EF 9 introduced and methods, which provide a convenient way of seeding the database with initial data. These methods aim to improve the experience of using custom initialization logic (explained below). They provide one clear location where all the data seeding code can be placed. Moreover, the code inside and methods is protected by the [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) to prevent concurrency issues. The new seeding methods are called as part of operation, and `dotnet ef database update` command, even if there are no model changes and no migrations were applied. diff --git a/entity-framework/core/providers/sqlite/limitations.md b/entity-framework/core/providers/sqlite/limitations.md index 6107b6fce7..aa944ac27f 100644 --- a/entity-framework/core/providers/sqlite/limitations.md +++ b/entity-framework/core/providers/sqlite/limitations.md @@ -92,9 +92,17 @@ dotnet ef database update --connection "Data Source=My.db" ## Concurrent migrations protection -EF9 introduced a locking mechanism when executing migrations. It aims to protect against multiple migration executions happening simultaneously, as that could leave the database in a corrupted state. This is one of the potential problems resulting from applying migrations at runtime using the method (see [Applying migrations](xref:core/managing-schemas/migrations/applying) for more information). To mitigate this, EF creates an exclusive lock on the database before any migration operations are applied. +EF9 introduced a [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) when executing migrations. It aims to protect against multiple migration executions happening simultaneously, as that could leave the database in a corrupted state. This is one of the potential problems resulting from applying migrations at runtime using the method (see [Applying migrations](xref:core/managing-schemas/migrations/applying) for more information). To mitigate this, EF creates an exclusive lock on the database before any migration operations are applied. -Unfortunately, SQLite does not have built-in locking mechanism, so EF Core creates a separate table (`__EFMigrationsLock`) and uses it for locking. The lock is released when the migration completes and the seeding code finishes execution. However, if for some reason migration fails in a non-recoverable way, the lock may not be released correctly. If this happens, consecutive migrations will be blocked from executing SQL and therefore never complete. You can manually unblock them by deleting the `__EFMigrationsLock` table in the database. +Unfortunately, SQLite does not have a built-in application locking mechanism, so EF Core creates a separate table (`__EFMigrationsLock`) and uses it for locking. A row is inserted to acquire the lock and deleted to release it when the migration completes and the seeding code finishes execution. However, if the application terminates unexpectedly during migration (for example, the process is killed), the lock may not be released. If this happens, subsequent migration attempts will wait indefinitely for the lock. + +To resolve an abandoned lock, delete the `__EFMigrationsLock` table or its rows from the database: + +```sql +DROP TABLE "__EFMigrationsLock"; +``` + +For more information, see [Handling abandoned locks](xref:core/managing-schemas/migrations/applying#handling-abandoned-locks). ## See also diff --git a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md index 001f1c8931..a0079a4672 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md @@ -1088,6 +1088,8 @@ The above were only some of the more important query improvements in EF9; see [t EF9 introduces a locking mechanism to protect against multiple migration executions happening simultaneously, as that could leave the database in a corrupted state. This doesn't happen when migrations are deployed to the production environment using [recommended methods](/ef/core/managing-schemas/migrations/applying#sql-scripts), but can happen if migrations are applied at runtime using the [`DbContext.Database.MigrateAsync()`](/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.migrate) method. We recommend applying migrations at deployment, rather than as part of application startup, but that can result in more complicated application architectures (e.g. [when using .NET Aspire projects](/dotnet/aspire/database/ef-core-migrations)). +For more information, see [Migration locking](/ef/core/managing-schemas/migrations/applying#migration-locking). + > [!NOTE] > If you are using Sqlite database, see [potential issues associated with this feature](/ef/core/providers/sqlite/limitations#concurrent-migrations-protection). From a5674fd599ff39db5ef223c415d8467c8215a9b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:46:56 +0000 Subject: [PATCH 3/5] Address review feedback: reduce SQLite section, fix exception wording, make code sample self-contained Agent-Logs-Url: https://github.com/dotnet/EntityFramework.Docs/sessions/746024ca-0b2a-4a41-aae0-d72cadbd0353 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../core/managing-schemas/migrations/applying.md | 6 +++--- .../core/providers/sqlite/limitations.md | 12 +----------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/entity-framework/core/managing-schemas/migrations/applying.md b/entity-framework/core/managing-schemas/migrations/applying.md index b3b87f0b8d..2167e7e793 100644 --- a/entity-framework/core/managing-schemas/migrations/applying.md +++ b/entity-framework/core/managing-schemas/migrations/applying.md @@ -390,13 +390,13 @@ After clearing the lock, subsequent migration operations proceed normally. The t ### Migrations and explicit transactions -Starting with EF Core 9, calling inside an explicit transaction throws a warning by default. This is because wrapping migrations in an external transaction prevents the migration lock from being acquired, which leaves the database unprotected from concurrent migration applications. EF Core manages its own transactions internally as needed during migration. +Starting with EF Core 9, calling inside an explicit transaction throws an exception by default. This is because wrapping migrations in an external transaction prevents the migration lock from being acquired, which leaves the database unprotected from concurrent migration applications. EF Core manages its own transactions internally as needed during migration. If you were previously wrapping `MigrateAsync()` in an explicit transaction: ```csharp -// This will throw a warning in EF Core 9+ -await strategy.ExecuteAsync(async () => +// This will throw an exception in EF Core 9+ +await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () => { await using var transaction = await dbContext.Database.BeginTransactionAsync(); await dbContext.Database.MigrateAsync(); diff --git a/entity-framework/core/providers/sqlite/limitations.md b/entity-framework/core/providers/sqlite/limitations.md index aa944ac27f..3bcbf44a45 100644 --- a/entity-framework/core/providers/sqlite/limitations.md +++ b/entity-framework/core/providers/sqlite/limitations.md @@ -92,17 +92,7 @@ dotnet ef database update --connection "Data Source=My.db" ## Concurrent migrations protection -EF9 introduced a [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) when executing migrations. It aims to protect against multiple migration executions happening simultaneously, as that could leave the database in a corrupted state. This is one of the potential problems resulting from applying migrations at runtime using the method (see [Applying migrations](xref:core/managing-schemas/migrations/applying) for more information). To mitigate this, EF creates an exclusive lock on the database before any migration operations are applied. - -Unfortunately, SQLite does not have a built-in application locking mechanism, so EF Core creates a separate table (`__EFMigrationsLock`) and uses it for locking. A row is inserted to acquire the lock and deleted to release it when the migration completes and the seeding code finishes execution. However, if the application terminates unexpectedly during migration (for example, the process is killed), the lock may not be released. If this happens, subsequent migration attempts will wait indefinitely for the lock. - -To resolve an abandoned lock, delete the `__EFMigrationsLock` table or its rows from the database: - -```sql -DROP TABLE "__EFMigrationsLock"; -``` - -For more information, see [Handling abandoned locks](xref:core/managing-schemas/migrations/applying#handling-abandoned-locks). +EF9 introduced a [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) to protect against concurrent migration executions. Unlike SQL Server, SQLite doesn't have built-in application locks, so EF Core uses a `__EFMigrationsLock` table instead. If the application terminates unexpectedly, this lock may not be released; see [Handling abandoned locks](xref:core/managing-schemas/migrations/applying#handling-abandoned-locks) for details. ## See also From 1981922f005b8ceb4dbb8dfc8dc457d4f21002ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 16:43:22 +0000 Subject: [PATCH 4/5] Move provider details to SQLite limitations, strengthen wording, simplify transaction section Agent-Logs-Url: https://github.com/dotnet/EntityFramework.Docs/sessions/37325281-a3df-43c1-95d6-2fb1a37c55c9 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../managing-schemas/migrations/applying.md | 64 ++----------------- .../core/providers/sqlite/limitations.md | 20 +++++- 2 files changed, 24 insertions(+), 60 deletions(-) diff --git a/entity-framework/core/managing-schemas/migrations/applying.md b/entity-framework/core/managing-schemas/migrations/applying.md index 2167e7e793..0f909df7db 100644 --- a/entity-framework/core/managing-schemas/migrations/applying.md +++ b/entity-framework/core/managing-schemas/migrations/applying.md @@ -355,64 +355,10 @@ Migration locking applies when migrations are applied using any of the following [SQL scripts](#sql-scripts) are not affected by migration locking, since they are applied outside of EF Core. -> [!NOTE] -> If you are using a third-party database provider, check the provider documentation to understand the locking behavior specific to your database. The information below covers the SQL Server and SQLite providers included with EF Core. - -### How the lock works - -The locking mechanism is provider-specific: - -* **SQL Server** uses `sp_getapplock` to acquire a session-level application lock named `__EFMigrationsLock`. This lock is automatically released when the database connection is closed, so it cannot become abandoned even if the application terminates unexpectedly. -* **SQLite** does not have a built-in application locking mechanism, so EF Core creates a `__EFMigrationsLock` table and uses it to coordinate access. A row is inserted to acquire the lock and deleted to release it. This lock requires explicit release (see [Handling abandoned locks](#handling-abandoned-locks)). - -If another migration execution is already in progress, the new request waits until the lock becomes available. - -### Handling abandoned locks - -In most cases, the migration lock is released automatically when the operation completes or when the connection is closed. However, on **SQLite**, if the application terminates unexpectedly (for example, the process is killed during migration), the lock row in the `__EFMigrationsLock` table may not be cleaned up. This prevents any subsequent migration from completing, because each attempt will wait indefinitely for the lock to be released. - -To resolve an abandoned lock on SQLite, delete the `__EFMigrationsLock` table from the database: - -```sql -DROP TABLE "__EFMigrationsLock"; -``` - -Or, alternatively, delete all rows from the table: - -```sql -DELETE FROM "__EFMigrationsLock"; -``` - -After clearing the lock, subsequent migration operations proceed normally. The table is automatically recreated as needed. - -> [!NOTE] -> This issue is specific to the SQLite provider. On SQL Server, the lock is session-based and is automatically released when the connection closes, so abandoned locks are not a concern. - -### Migrations and explicit transactions - -Starting with EF Core 9, calling inside an explicit transaction throws an exception by default. This is because wrapping migrations in an external transaction prevents the migration lock from being acquired, which leaves the database unprotected from concurrent migration applications. EF Core manages its own transactions internally as needed during migration. - -If you were previously wrapping `MigrateAsync()` in an explicit transaction: - -```csharp -// This will throw an exception in EF Core 9+ -await dbContext.Database.CreateExecutionStrategy().ExecuteAsync(async () => -{ - await using var transaction = await dbContext.Database.BeginTransactionAsync(); - await dbContext.Database.MigrateAsync(); - await transaction.CommitAsync(); -}); -``` - -Remove the external transaction and `ExecutionStrategy`: - -```csharp -// Recommended: let EF Core manage transactions -await dbContext.Database.MigrateAsync(); -``` +> [!WARNING] +> The locking mechanism varies significantly across database providers and can involve provider-specific gotchas. For example, the SQLite provider uses a lock table that can become [abandoned if the process terminates unexpectedly](xref:core/providers/sqlite/limitations#concurrent-migrations-protection). Always consult your provider's documentation for details. -If your scenario requires an explicit transaction and you have another mechanism in place to prevent concurrent migration application, you can suppress the warning: +### Limitations -```csharp -options.ConfigureWarnings(w => w.Ignore(RelationalEventId.MigrationsUserTransactionWarning)) -``` +* Wrapping in an explicit transaction is not supported. See [Exception is thrown when applying migrations in an explicit transaction](xref:core/what-is-new/ef-core-9.0/breaking-changes#migrations-transaction) for details. +* On SQLite, abandoned migration locks can [block subsequent migrations](xref:core/providers/sqlite/limitations#concurrent-migrations-protection). diff --git a/entity-framework/core/providers/sqlite/limitations.md b/entity-framework/core/providers/sqlite/limitations.md index 3bcbf44a45..264a5a8028 100644 --- a/entity-framework/core/providers/sqlite/limitations.md +++ b/entity-framework/core/providers/sqlite/limitations.md @@ -92,7 +92,25 @@ dotnet ef database update --connection "Data Source=My.db" ## Concurrent migrations protection -EF9 introduced a [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) to protect against concurrent migration executions. Unlike SQL Server, SQLite doesn't have built-in application locks, so EF Core uses a `__EFMigrationsLock` table instead. If the application terminates unexpectedly, this lock may not be released; see [Handling abandoned locks](xref:core/managing-schemas/migrations/applying#handling-abandoned-locks) for details. +EF9 introduced a [migration locking mechanism](xref:core/managing-schemas/migrations/applying#migration-locking) to protect against concurrent migration executions. Unlike SQL Server, which uses a session-level application lock (`sp_getapplock`) that is automatically released when the connection closes, SQLite doesn't have built-in application locks. EF Core instead creates a `__EFMigrationsLock` table and inserts a row to acquire the lock. + +### Handling abandoned locks + +If the application terminates unexpectedly (for example, the process is killed during migration), the lock row in the `__EFMigrationsLock` table may not be cleaned up. This prevents any subsequent migration from completing, because each attempt will wait indefinitely for the lock to be released. + +To resolve an abandoned lock, drop the `__EFMigrationsLock` table from the database: + +```sql +DROP TABLE "__EFMigrationsLock"; +``` + +Or, alternatively, delete all rows from the table: + +```sql +DELETE FROM "__EFMigrationsLock"; +``` + +After clearing the lock, subsequent migration operations proceed normally. The table is automatically recreated as needed. ## See also From db4f807275f4fcfcfbeb4f755f8ffb9459c81ecc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:37:31 +0000 Subject: [PATCH 5/5] Update ms.date in applying.md and limitations.md; reword warning to avoid 'gotchas' Agent-Logs-Url: https://github.com/dotnet/EntityFramework.Docs/sessions/8ff97942-e08a-4cb3-99c5-0b1bcf2ebed1 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- entity-framework/core/managing-schemas/migrations/applying.md | 4 ++-- entity-framework/core/providers/sqlite/limitations.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/entity-framework/core/managing-schemas/migrations/applying.md b/entity-framework/core/managing-schemas/migrations/applying.md index 0f909df7db..15db8f7460 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: 10/29/2024 +ms.date: 04/16/2026 uid: core/managing-schemas/migrations/applying ms.custom: sfi-ropc-nochange --- @@ -356,7 +356,7 @@ Migration locking applies when migrations are applied using any of the following [SQL scripts](#sql-scripts) are not affected by migration locking, since they are applied outside of EF Core. > [!WARNING] -> The locking mechanism varies significantly across database providers and can involve provider-specific gotchas. For example, the SQLite provider uses a lock table that can become [abandoned if the process terminates unexpectedly](xref:core/providers/sqlite/limitations#concurrent-migrations-protection). Always consult your provider's documentation for details. +> The locking mechanism varies significantly across database providers and can involve provider-specific issues. For example, the SQLite provider uses a lock table that can become [abandoned if the process terminates unexpectedly](xref:core/providers/sqlite/limitations#concurrent-migrations-protection). Always consult your provider's documentation for details. ### Limitations diff --git a/entity-framework/core/providers/sqlite/limitations.md b/entity-framework/core/providers/sqlite/limitations.md index 264a5a8028..4ce6c2fee6 100644 --- a/entity-framework/core/providers/sqlite/limitations.md +++ b/entity-framework/core/providers/sqlite/limitations.md @@ -2,7 +2,7 @@ title: SQLite Database Provider - Limitations - EF Core description: Limitations of the Entity Framework Core SQLite database provider as compared to other providers author: SamMonoRT -ms.date: 11/15/2021 +ms.date: 04/16/2026 uid: core/providers/sqlite/limitations --- # SQLite EF Core Database Provider Limitations