diff --git a/NuGet.config b/NuGet.config
index b8e45392bc6..11bcc2d5e56 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -19,10 +19,6 @@
-
-
-
-
diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
index d9d8dea9f5f..042e8744a7d 100644
--- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
+++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
@@ -283,7 +283,7 @@ private bool LocalDetectChanges(InternalEntryBase entry)
}
}
- foreach (var complexProperty in entry.StructuralType.GetComplexProperties())
+ foreach (var complexProperty in entry.StructuralType.GetFlattenedComplexProperties())
{
if (complexProperty.IsCollection)
{
diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalComplexEntryTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalComplexEntryTest.cs
index f0089b8d870..ef361b1a753 100644
--- a/test/EFCore.Tests/ChangeTracking/Internal/InternalComplexEntryTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalComplexEntryTest.cs
@@ -674,6 +674,51 @@ public void GetEntry_throws_when_accessing_invalid_current_ordinal()
Assert.Equal(CoreStrings.ComplexCollectionEntryOrdinalInvalid(5, "Blog", "Tags", 1), ex.Message);
}
+ [ConditionalFact]
+ public void DetectChanges_detects_changes_in_nested_complex_collections()
+ {
+ var model = CreateModelWithNestedComplexCollections();
+ var entityType = model.FindEntityType(typeof(BlogWithNested))!;
+
+ var serviceProvider = InMemoryTestHelpers.Instance.CreateContextServices(model);
+ var stateManager = serviceProvider.GetRequiredService();
+ var changeDetector = serviceProvider.GetRequiredService();
+
+ var blog = new BlogWithNested
+ {
+ NestedJson = new NestedJson
+ {
+ Item = new NestedItem { Name = "foo" },
+ Items =
+ [
+ new NestedItem { Name = "bar" },
+ new NestedItem { Name = "baz" }
+ ]
+ }
+ };
+
+ var entityEntry = stateManager.GetOrCreateEntry(blog);
+ entityEntry.SetEntityState(EntityState.Unchanged);
+
+ Assert.Equal(EntityState.Unchanged, entityEntry.EntityState);
+
+ // Replace the NestedJson with a new instance that has a modified Items collection
+ blog.NestedJson = blog.NestedJson with
+ {
+ Items =
+ [
+ new NestedItem { Name = "bar" },
+ new NestedItem { Name = "baz" },
+ new NestedItem { Name = "new-bar" }
+ ]
+ };
+
+ // DetectChanges should detect the change in the nested complex collection
+ changeDetector.DetectChanges(stateManager);
+
+ Assert.Equal(EntityState.Modified, entityEntry.EntityState);
+ }
+
private static IModel CreateModel()
{
var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();
@@ -688,6 +733,22 @@ private static IModel CreateModel()
return modelBuilder.FinalizeModel();
}
+ private static IModel CreateModelWithNestedComplexCollections()
+ {
+ var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();
+
+ modelBuilder.Entity(eb =>
+ {
+ eb.ComplexProperty(e => e.NestedJson, b =>
+ {
+ b.ComplexProperty(a => a.Item);
+ b.ComplexCollection(a => a.Items);
+ });
+ });
+
+ return modelBuilder.FinalizeModel();
+ }
+
private class Blog
{
public int Id { get; set; }
@@ -714,4 +775,22 @@ private class Category
public string Name { get; set; } = "";
public string Description { get; set; } = "";
}
+
+ private class BlogWithNested
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public NestedJson NestedJson { get; set; } = new();
+ }
+
+ private record NestedJson
+ {
+ public NestedItem Item { get; init; } = new();
+ public List Items { get; init; } = [];
+ }
+
+ private record NestedItem
+ {
+ public string Name { get; init; } = "";
+ }
}