From e3c82773696dbbbb27e93ebb1d03a957e34ea0ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:13:47 +0000 Subject: [PATCH 1/2] Document DbQueryConcurrencyException breaking change for split queries --- .../ef-core-11.0/breaking-changes.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md index f4e3c56230..d4edb27b8c 100644 --- a/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md +++ b/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md @@ -33,6 +33,7 @@ This page documents API and behavior changes that have the potential to break ex | [Cosmos: the default discriminator property is now named `Discriminator` in the model](#cosmos-discriminator-property-name) | Low | | [Owned JSON collections without an explicit key are obsolete](#owned-json-collections-obsolete) | Low | | [`Property` no longer configures primitive collections](#property-not-primitive-collection) | Low | +| [Split queries now throw when concurrent modifications are detected](#split-query-concurrency-exception) | Low | ## Medium-impact changes @@ -479,6 +480,69 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) In most cases no change is required, since primitive collections are discovered by convention. + + +### Split queries now throw when concurrent modifications are detected + +[Tracking Issue #33826](https://github.com/dotnet/efcore/issues/33826) + +#### Old behavior + +Previously, when a split query (using `AsSplitQuery()`) encountered out-of-order or orphaned child rows caused by concurrent data modifications between the split query's SQL statements, EF Core silently discarded the affected child collections. The result was an entity with an empty collection even though the parent and its related data were never modified—no exception was thrown and no warning was logged. + +#### New behavior + +Starting with EF Core 11.0, EF Core throws a `DbQueryConcurrencyException` when split query results cannot be correlated because of concurrent data modifications. The exception message describes the situation and suggests remediation: + +> The results of a split query could not be correlated because the data was modified concurrently while the query was executing. Re-execute the query, or execute it within a serializable or snapshot transaction to prevent concurrent modifications. + +#### Why + +Silently returning incorrect data (empty collections for entities that have related rows) is far worse than surfacing an error. This scenario is intrinsically caused by the lack of data-consistency guarantees in split queries when the database is modified between statements. Throwing a retriable exception makes the problem visible and gives callers a clear path to recovery. + +#### Mitigations + +The simplest mitigation is to re-execute the query; the concurrent modification is transient and the retry will typically succeed: + +```csharp +List blogs; +while (true) +{ + try + { + blogs = await context.Blogs + .Include(b => b.Posts) + .AsSplitQuery() + .ToListAsync(); + break; + } + catch (DbQueryConcurrencyException) + { + // Retry on concurrent modification + } +} +``` + +Alternatively, wrap the split query in a serializable or snapshot transaction to prevent concurrent modifications from affecting the results: + +```csharp +await using var transaction = await context.Database.BeginTransactionAsync(IsolationLevel.Snapshot); + +var blogs = await context.Blogs + .Include(b => b.Posts) + .AsSplitQuery() + .ToListAsync(); +``` + +If neither retry nor a transaction is acceptable, switch to a single query (`AsSingleQuery()`) which is always consistent: + +```csharp +var blogs = await context.Blogs + .Include(b => b.Posts) + .AsSingleQuery() + .ToListAsync(); +``` + ## Microsoft.Data.Sqlite breaking changes From bbdb2437e81048aa491e43edf9547979aa4ff44d Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 28 Jul 2026 17:40:52 -0700 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../what-is-new/ef-core-11.0/breaking-changes.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md b/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md index d4edb27b8c..94472b4b37 100644 --- a/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md +++ b/entity-framework/core/what-is-new/ef-core-11.0/breaking-changes.md @@ -488,7 +488,7 @@ In most cases no change is required, since primitive collections are discovered #### Old behavior -Previously, when a split query (using `AsSplitQuery()`) encountered out-of-order or orphaned child rows caused by concurrent data modifications between the split query's SQL statements, EF Core silently discarded the affected child collections. The result was an entity with an empty collection even though the parent and its related data were never modified—no exception was thrown and no warning was logged. +Previously, when a split query (using `AsSplitQuery()`) encountered out-of-order or orphaned child rows caused by concurrent data modifications between the split query's SQL statements, EF Core silently discarded the affected child collections. The result was an entity with an empty collection even though the related rows still existed—no exception was thrown and no warning was logged. #### New behavior @@ -505,8 +505,10 @@ Silently returning incorrect data (empty collections for entities that have rela The simplest mitigation is to re-execute the query; the concurrent modification is transient and the retry will typically succeed: ```csharp +const int maxRetries = 3; + List blogs; -while (true) +for (var attempt = 0; attempt < maxRetries; attempt++) { try { @@ -516,7 +518,7 @@ while (true) .ToListAsync(); break; } - catch (DbQueryConcurrencyException) + catch (DbQueryConcurrencyException) when (attempt < maxRetries - 1) { // Retry on concurrent modification } @@ -526,12 +528,15 @@ while (true) Alternatively, wrap the split query in a serializable or snapshot transaction to prevent concurrent modifications from affecting the results: ```csharp -await using var transaction = await context.Database.BeginTransactionAsync(IsolationLevel.Snapshot); +await using var transaction = + await context.Database.BeginTransactionAsync(IsolationLevel.Serializable); var blogs = await context.Blogs .Include(b => b.Posts) .AsSplitQuery() .ToListAsync(); + +await transaction.CommitAsync(); ``` If neither retry nor a transaction is acceptable, switch to a single query (`AsSingleQuery()`) which is always consistent: