Fix RowVersion concurrency issue when replacing entities with TPH inheritance and owned types#37788
Draft
Fix RowVersion concurrency issue when replacing entities with TPH inheritance and owned types#37788
Conversation
…n bug Add test reproducing a bug where deleting EntityA (with owned type) and adding EntityB (same PK) in TPH generates an UPDATE command that may interfere with RowVersion concurrency token handling when table sharing is involved due to owned entity types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t for issue #37169 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix RowVersion concurrency issue with inheritance and owned types
Fix RowVersion concurrency issue when replacing entities with TPH inheritance and owned types
Feb 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When deleting an entity with an owned type and inserting a sibling entity with the same PK in a single
SaveChanges(TPH + concurrency token), EF Core generates two UPDATE statements for the same row, causingDbUpdateConcurrencyException.Root Cause
In
SharedTableEntryMap.GetMainEntry(), when entity replacement occurs (delete EntityA + add EntityB, same PK),FindPrincipal()locates EntityB in the identity map (it replaced EntityA) butFilterIncompatiblePrincipal()filters it out becauseEntityA.IsAssignableFrom(EntityB)is false—they're sibling types. This causes the owned entity to fall out ofGetMainEntry()as its own main entry, producing a separateModificationCommand(initialized asModifiedby the constructor). Two UPDATEs then target the same row; the second fails because the first already changed the RowVersion.Changes
CommandBatchPreparerTest: sets up TPH model withEntityBase(RowVersion concurrency token),EntityA(ownsOwnedEntity), andEntityB; verifies that deleting EntityA and adding EntityB with the same PK produces a singleModifiedcommand rather than two conflicting ones.Original prompt
This section details on the original issue you should resolve
<issue_title>RowVersion concurrency issue when replacing entities with inheritance and owned types</issue_title>
<issue_description>### Bug description
The issue can best be demonstrated using a simple sample application. Explaining it purely in text is relatively difficult.
ConsoleApp.zip
The structure of the sample application is as follows:
When the following steps are performed:
the following exception is thrown:
'Unhandled exception. Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)'
The issue only occurs with this specific combination of:
The problem can be worked around by either:
Your code
Stack traces