From 9e06aa0ba64c7ab61730a9f861d4333e93097275 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 04:52:49 +0000 Subject: [PATCH 1/4] Initial plan From 0cfbc1d19438cd39328a78d96de02f8d0b407beb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 04:59:02 +0000 Subject: [PATCH 2/4] Port PR #36769 to main: Fix FK dependency ordering when replacing owned entities Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Update/Internal/CommandBatchPreparer.cs | 8 +- .../Update/CommandBatchPreparerTest.cs | 101 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 6590593a40e..08690f863dc 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -881,7 +881,13 @@ private static bool CanCreateDependency(IForeignKey foreignKey, IReadOnlyModific return false; } - if (foreignKey.GetMappedConstraints().Any(c => (principal ? c.PrincipalTable : c.Table) == command.Table)) + // Special case: For owned entities that have FK relationships to other entities, + // we need to ensure dependencies are created even if the FK constraint exists. + // This is needed to fix FK dependency ordering when replacing owned entities. + var isOwnedEntityFKToNonOwner = foreignKey.DeclaringEntityType.IsOwned() + && foreignKey.PrincipalEntityType != foreignKey.DeclaringEntityType.FindOwnership()?.PrincipalEntityType; + + if (!isOwnedEntityFKToNonOwner && foreignKey.GetMappedConstraints().Any(c => (principal ? c.PrincipalTable : c.Table) == command.Table)) { // Handled elsewhere return false; diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index c02cf09ca18..e36fc52aa09 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -1206,9 +1206,110 @@ public void BatchCommands_handles_null_values_when_sensitive_logging_enabled() Assert.DoesNotContain("Object reference not set", exception.Message); } + [ConditionalFact] + public void BatchCommands_sorts_FK_dependencies_correctly_when_replacing_owned_entity() + { + // Reproduces issue #36059: FK dependency ordering wrong when replacing an inline owned entity + var model = CreateOwnedEntityWithFKModel(); + var configuration = CreateContextServices(model); + var stateManager = configuration.GetRequiredService(); + + // Create original content and document + var originalContent = new ContentEntity { Id = 1, Data = "original data" }; + var document = new DocumentEntity { Id = 1, Name = "Test Doc", FileId = 1, FileName = "original.txt", FileContentId = 1 }; + + var originalContentEntry = stateManager.GetOrCreateEntry(originalContent); + originalContentEntry.SetEntityState(EntityState.Unchanged); + + var documentEntry = stateManager.GetOrCreateEntry(document); + documentEntry.SetEntityState(EntityState.Unchanged); + + // Now create new content + var newContent = new ContentEntity { Id = 2, Data = "new data" }; + var newContentEntry = stateManager.GetOrCreateEntry(newContent); + newContentEntry.SetEntityState(EntityState.Added); + + // Simulate replacing the owned entity by updating document properties to reference new content + document.FileId = 2; + document.FileName = "new.txt"; + document.FileContentId = 2; + documentEntry.SetEntityState(EntityState.Modified); + + // Delete the original content + originalContentEntry.SetEntityState(EntityState.Deleted); + + var modelData = new UpdateAdapter(stateManager); + var batches = CreateBatches([originalContentEntry, newContentEntry, documentEntry], modelData); + var commands = batches.SelectMany(b => b.ModificationCommands).ToList(); + + // Find the commands + var insertContentCmd = commands.FirstOrDefault(c => c.EntityState == EntityState.Added && c.TableName == "ContentEntity"); + var updateDocumentCmd = commands.FirstOrDefault(c => c.EntityState == EntityState.Modified && c.TableName == "DocumentEntity"); + var deleteContentCmd = commands.FirstOrDefault(c => c.EntityState == EntityState.Deleted && c.TableName == "ContentEntity"); + + Assert.NotNull(insertContentCmd); + Assert.NotNull(updateDocumentCmd); + Assert.NotNull(deleteContentCmd); + + var insertIndex = commands.IndexOf(insertContentCmd); + var updateIndex = commands.IndexOf(updateDocumentCmd); + var deleteIndex = commands.IndexOf(deleteContentCmd); + + // The correct order should be: INSERT new content, UPDATE document, DELETE old content + // This ensures the FK constraint is not violated + Assert.True(insertIndex < updateIndex, + $"INSERT Content should come before UPDATE Document, but got INSERT at {insertIndex} and UPDATE at {updateIndex}"); + Assert.True(updateIndex < deleteIndex, + $"UPDATE Document should come before DELETE Content, but got UPDATE at {updateIndex} and DELETE at {deleteIndex}"); + } + private class AnotherFakeEntity { public int Id { get; set; } public int? AnotherId { get; set; } } + + private class DocumentEntity + { + public int Id { get; set; } + public string Name { get; set; } + // Owned File entity properties (flattened) + public int? FileId { get; set; } + public string FileName { get; set; } + public int? FileContentId { get; set; } + } + + private class ContentEntity + { + public int Id { get; set; } + public string Data { get; set; } + } + + private static IModel CreateOwnedEntityWithFKModel() + { + var modelBuilder = FakeRelationalTestHelpers.Instance.CreateConventionBuilder(); + + modelBuilder.Entity(b => + { + b.HasKey(d => d.Id); + b.Property(d => d.Name); + b.Property(d => d.FileId); + b.Property(d => d.FileName); + b.Property(d => d.FileContentId); + }); + + modelBuilder.Entity(b => + { + b.HasKey(c => c.Id); + b.Property(c => c.Data); + }); + + // Add FK relationship from Document to Content through FileContentId + modelBuilder.Entity() + .HasOne() + .WithMany() + .HasForeignKey(d => d.FileContentId); + + return modelBuilder.Model.FinalizeModel(); + } } From 5f7830aac35c873475201077a0f37735f9d7ae5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 05:06:53 +0000 Subject: [PATCH 3/4] Improve comment accuracy in test Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index e36fc52aa09..73ecf815991 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -1273,7 +1273,7 @@ private class DocumentEntity { public int Id { get; set; } public string Name { get; set; } - // Owned File entity properties (flattened) + // Properties that would typically come from an owned File entity (inlined into Document table) public int? FileId { get; set; } public string FileName { get; set; } public int? FileContentId { get; set; } From 7fd2d9ccc8aec51da89375ba98cfad41ed24956d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 22:43:35 +0000 Subject: [PATCH 4/4] Fix test to use actual owned entities - test now fails without fix Modified the test to use a proper owned entity (FileEntity) with an FK to a non-owner entity (ContentEntity). The test now properly reproduces issue #36059 and fails without the fix. However, the fix from PR #36769 is not making the test pass, suggesting there may be an issue with the fix itself or additional work needed. Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Update/CommandBatchPreparerTest.cs | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs index 73ecf815991..f63af065346 100644 --- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs +++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs @@ -1209,30 +1209,33 @@ public void BatchCommands_handles_null_values_when_sensitive_logging_enabled() [ConditionalFact] public void BatchCommands_sorts_FK_dependencies_correctly_when_replacing_owned_entity() { - // Reproduces issue #36059: FK dependency ordering wrong when replacing an inline owned entity + // Reproduces issue #36059: FK dependency ordering wrong when replacing an owned entity with FK to non-owner var model = CreateOwnedEntityWithFKModel(); var configuration = CreateContextServices(model); var stateManager = configuration.GetRequiredService(); - // Create original content and document + // Create original content var originalContent = new ContentEntity { Id = 1, Data = "original data" }; - var document = new DocumentEntity { Id = 1, Name = "Test Doc", FileId = 1, FileName = "original.txt", FileContentId = 1 }; - var originalContentEntry = stateManager.GetOrCreateEntry(originalContent); originalContentEntry.SetEntityState(EntityState.Unchanged); + // Create document with owned file that references original content + var document = new DocumentEntity + { + Id = 1, + Name = "Test Doc", + File = new FileEntity { Id = 1, FileName = "original.txt", ContentId = 1 } + }; var documentEntry = stateManager.GetOrCreateEntry(document); documentEntry.SetEntityState(EntityState.Unchanged); - // Now create new content + // Create new content var newContent = new ContentEntity { Id = 2, Data = "new data" }; var newContentEntry = stateManager.GetOrCreateEntry(newContent); newContentEntry.SetEntityState(EntityState.Added); - // Simulate replacing the owned entity by updating document properties to reference new content - document.FileId = 2; - document.FileName = "new.txt"; - document.FileContentId = 2; + // Replace the owned file - manually mark document as modified + document.File = new FileEntity { Id = 2, FileName = "new.txt", ContentId = 2 }; documentEntry.SetEntityState(EntityState.Modified); // Delete the original content @@ -1255,7 +1258,7 @@ public void BatchCommands_sorts_FK_dependencies_correctly_when_replacing_owned_e var updateIndex = commands.IndexOf(updateDocumentCmd); var deleteIndex = commands.IndexOf(deleteContentCmd); - // The correct order should be: INSERT new content, UPDATE document, DELETE old content + // The correct order should be: INSERT new content, UPDATE document (with new file), DELETE old content // This ensures the FK constraint is not violated Assert.True(insertIndex < updateIndex, $"INSERT Content should come before UPDATE Document, but got INSERT at {insertIndex} and UPDATE at {updateIndex}"); @@ -1273,10 +1276,14 @@ private class DocumentEntity { public int Id { get; set; } public string Name { get; set; } - // Properties that would typically come from an owned File entity (inlined into Document table) - public int? FileId { get; set; } + public FileEntity File { get; set; } + } + + private class FileEntity + { + public int Id { get; set; } public string FileName { get; set; } - public int? FileContentId { get; set; } + public int? ContentId { get; set; } } private class ContentEntity @@ -1293,9 +1300,19 @@ private static IModel CreateOwnedEntityWithFKModel() { b.HasKey(d => d.Id); b.Property(d => d.Name); - b.Property(d => d.FileId); - b.Property(d => d.FileName); - b.Property(d => d.FileContentId); + + // Configure File as owned entity + b.OwnsOne(d => d.File, fb => + { + fb.Property(f => f.Id); + fb.Property(f => f.FileName); + fb.Property(f => f.ContentId); + + // Add FK from owned File to non-owned Content + fb.HasOne() + .WithMany() + .HasForeignKey(f => f.ContentId); + }); }); modelBuilder.Entity(b => @@ -1304,12 +1321,6 @@ private static IModel CreateOwnedEntityWithFKModel() b.Property(c => c.Data); }); - // Add FK relationship from Document to Content through FileContentId - modelBuilder.Entity() - .HasOne() - .WithMany() - .HasForeignKey(d => d.FileContentId); - return modelBuilder.Model.FinalizeModel(); } }