{"record":{"id":"e639080e51bb02e6","repo":"dotnet/efcore","slug":"splitqueryconcurrentmodification","errorCode":"SplitQueryConcurrentModification","errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"DbQueryConcurrencyException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/Internal/SplitQueryResultCoordinator.cs","lineNumber":104,"sourceCode":"\n    /// <summary>\n    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to\n    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in\n    ///     any release. You should only use it directly in your code with extreme caution and knowing that\n    ///     doing so can result in application failures when updating to a new Entity Framework Core release.\n    /// </summary>\n    public void VerifyNoOrphanedChildRows()\n    {\n        foreach (var dataReaderContext in DataReaders)\n        {\n            // Split collection queries correlate child rows to parents by consuming, for each parent (in order), the leading child\n            // rows whose parent key matches. HasNext == true here means the split reader is parked on a child row that didn't match\n            // the last parent - and since every parent has now been processed, that row (and any after it) belongs to no parent in\n            // the parent query's results. This can only happen if the data was modified concurrently between the execution of the\n            // parent query and the child query, leaving orphan child rows that would otherwise be silently dropped (see #33826).\n            if (dataReaderContext?.HasNext == true)\n            {\n                throw new DbQueryConcurrencyException(RelationalStrings.SplitQueryConcurrentModification);\n            }\n        }\n    }\n}\n","sourceCodeStart":86,"sourceCodeEnd":109,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/Internal/SplitQueryResultCoordinator.cs#L86-L109","documentation":"Thrown by SplitQueryResultCoordinator.VerifyNoOrphanedChildRows (SplitQueryResultCoordinator.cs:104) as a DbQueryConcurrencyException. Split queries run the parent query and each related collection as separate SQL statements, correlating child rows to parents by key order. After the last parent is processed, if a child reader still has rows (HasNext == true), those rows belong to no parent, meaning the data was modified concurrently between the parent and child queries. EF throws rather than silently drop orphaned rows.","triggerScenarios":"Running an AsSplitQuery() with related collections while another transaction inserts child rows whose parent key is not among the (already-fetched) parents, or deletes parents, between the parent and child query executions. Fires at the end of enumeration when VerifyNoOrphanedChildRows is called (GroupBySplitQueryingEnumerable / SplitQueryingEnumerable end-of-enumeration paths).","commonSituations":"High-concurrency apps using split queries without transaction isolation; background jobs inserting children during a read; deleting parents mid-query; race between read and write under default (read committed / no snapshot) isolation.","solutions":["Re-execute the query (transient races often resolve on retry).","Wrap the split query in a serializable or snapshot transaction so the read sees a stable point-in-time view.","Switch the query to AsSingleQuery() (a single JOINed statement) which has no parent/child correlation window.","Reduce the window between parent and child reads (fewer includes, smaller result sets).","Apply an optimistic-retry policy around the read for concurrency-induced occurrences."],"exampleFix":"// before - split query races against concurrent writes\nvar data = ctx.Orders.AsSplitQuery().Include(o => o.Items).ToList();\n// may throw SplitQueryConcurrentModification if items are inserted concurrently\n\n// after - snapshot transaction gives a stable view\nusing var tx = ctx.Database.BeginTransaction(System.Data.IsolationLevel.Snapshot);\nvar data = ctx.Orders.AsSplitQuery().Include(o => o.Items).ToList();\ntx.Commit();\n\n// or fall back to a single query (no correlation window)\nvar data = ctx.Orders.AsSingleQuery().Include(o => o.Items).ToList();","handlingStrategy":"retry","validationCode":"// Run split queries under a snapshot/serializable transaction to avoid concurrent-modification races.\nusing var tx = ctx.Database.BeginTransaction(IsolationLevel.Snapshot);\nvar data = ctx.Orders.AsSplitQuery().Include(o => o.Items).ToList();\ntx.Commit();\n// Or prefer AsSingleQuery() for writes-prone environments.","typeGuard":null,"tryCatchPattern":"// Retry transient split-query concurrent-modification failures.\nfor (int attempt = 0; ; attempt++) {\n    try {\n        using var tx = ctx.Database.BeginTransaction(IsolationLevel.Snapshot);\n        var data = ctx.Orders.AsSplitQuery().Include(o => o.Items).ToList();\n        tx.Commit();\n        return data;\n    } catch (DbQueryConcurrencyException ex) when (attempt < 3) {\n        // SplitQueryConcurrentModification: data changed concurrently; retry\n        await Task.Delay(100 * (attempt + 1));\n    }\n}","preventionTips":["Run split queries inside a snapshot/serializable transaction for a stable read view.","Use AsSingleQuery() when related collections are small or writes are frequent.","Retry transient occurrences; treat persistent ones as an isolation-config problem.","Avoid long-running read transactions that widen the modification window."],"tags":["ef-core","split-query","concurrency","transaction","isolation"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}