From 55a935cc72057a4c2451ec3a46389937dffd2984 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 07:54:39 +0000 Subject: [PATCH 1/4] Fix ComplexCollection ToJson default value in migrations to use [] instead of {} Co-authored-by: roji <1862641+roji@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/1fd85ba2-cb75-4e30-b1f5-afbf64cc4626 --- .../Internal/MigrationsModelDiffer.cs | 12 ++++- .../Internal/MigrationsModelDifferTest.cs | 46 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 91d50924a0e..3d167940175 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1279,11 +1279,21 @@ private void InitializeJsonColumn( columnOperation.ClrType = typeof(string); columnOperation.DefaultValue = inline || isNullable ? null - : "{}"; + : IsJsonCollectionColumn(jsonColumn) ? "[]" : "{}"; columnOperation.AddAnnotations(migrationsAnnotations); } + private static bool IsJsonCollectionColumn(JsonColumn jsonColumn) + => jsonColumn.Table.ComplexTypeMappings.Any( + m => m.TypeBase is IComplexType ct + && ct.GetContainerColumnName() == jsonColumn.Name + && ct.ComplexProperty.IsCollection) + || jsonColumn.Table.EntityTypeMappings.Any( + m => m.TypeBase is IEntityType et + && et.GetContainerColumnName() == jsonColumn.Name + && et.FindOwnership() is { IsUnique: false }); + #endregion #region IKey diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index aed949d96a0..bb58e7eed83 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -10007,6 +10007,52 @@ public virtual void Convert_table_from_owned_to_complex_properties_mapped_to_jso Assert.Empty); #pragma warning restore EF8001 // Owned JSON entities are obsolete + [ConditionalFact] + public virtual void Add_complex_collection_mapped_to_json_uses_empty_array_as_default_value() + => Execute( + _ => { }, + source => + { + source.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + }); + }, + target => + { + target.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + + e.ComplexCollection, MyJsonComplex>( + "ComplexCollection", cp => + { + cp.IsRequired(); + cp.ToJson("json_collection"); + cp.Property(x => x.Value); + cp.Property(x => x.Date); + }); + }); + }, + upOps => + { + Assert.Equal(1, upOps.Count); + + var operation = Assert.IsType(upOps[0]); + Assert.Equal("Entity", operation.Table); + Assert.Equal("json_collection", operation.Name); + Assert.Equal("[]", operation.DefaultValue); + }, + downOps => + { + Assert.Equal(1, downOps.Count); + Assert.IsType(downOps[0]); + }); + [ConditionalFact] public virtual void Noop_on_complex_properties() => Execute( From 48230845f1590f0d7c5b54cbcd59ef6ba722c450 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 21 Mar 2026 10:09:42 +0100 Subject: [PATCH 2/4] Add test for owned collection mapped to JSON Addresses review feedback: adds a test covering the owned (non-complex) collection case mapped to JSON. Owned collections are always nullable (IsUnique is false), so the default value is null rather than []. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Internal/MigrationsModelDifferTest.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index bb58e7eed83..fa12108dadf 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -10053,6 +10053,55 @@ public virtual void Add_complex_collection_mapped_to_json_uses_empty_array_as_de Assert.IsType(downOps[0]); }); +#pragma warning disable EF8001 // Owned JSON entities are obsolete + [ConditionalFact] + public virtual void Add_owned_collection_mapped_to_json_has_nullable_column() + => Execute( + _ => { }, + source => + { + source.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + }); + }, + target => + { + target.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + + e.OwnsMany( + "Owned", "json_collection", o => + { + o.ToJson(); + o.Property("Value"); + o.Property("Date"); + }); + }); + }, + upOps => + { + Assert.Equal(1, upOps.Count); + + var operation = Assert.IsType(upOps[0]); + Assert.Equal("Entity", operation.Table); + Assert.Equal("json_collection", operation.Name); + // Owned collections are always nullable (IsUnique is false), so no default value + Assert.True(operation.IsNullable); + Assert.Null(operation.DefaultValue); + }, + downOps => + { + Assert.Equal(1, downOps.Count); + Assert.IsType(downOps[0]); + }); +#pragma warning restore EF8001 // Owned JSON entities are obsolete + [ConditionalFact] public virtual void Noop_on_complex_properties() => Execute( From 7cdae14ad3d6863a267058e27d1d034591f07266 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 21 Mar 2026 16:23:30 +0100 Subject: [PATCH 3/4] Fix IsJsonCollectionColumn matching nested OwnsMany inside OwnsOne The IsJsonCollectionColumn check was incorrectly matching a nested OwnsMany inside an OwnsOne reference, because the nested entity's GetContainerColumnName() returns the root JSON column name and its ownership has IsUnique: false. This caused Convert_string_column_to_ a_json_column_containing_required_reference to fail with '[]' instead of '{}'. Fix: only consider root-level entity mappings whose principal is not itself mapped to JSON. Added a regression test for this case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Internal/MigrationsModelDiffer.cs | 3 +- .../Internal/MigrationsModelDifferTest.cs | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 3d167940175..37cfe9779c0 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1292,7 +1292,8 @@ private static bool IsJsonCollectionColumn(JsonColumn jsonColumn) || jsonColumn.Table.EntityTypeMappings.Any( m => m.TypeBase is IEntityType et && et.GetContainerColumnName() == jsonColumn.Name - && et.FindOwnership() is { IsUnique: false }); + && et.FindOwnership() is { IsUnique: false, PrincipalEntityType: var principal } + && !principal.IsMappedToJson()); #endregion diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index fa12108dadf..1395dcece3c 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -10102,6 +10102,59 @@ public virtual void Add_owned_collection_mapped_to_json_has_nullable_column() }); #pragma warning restore EF8001 // Owned JSON entities are obsolete +#pragma warning disable EF8001 // Owned JSON entities are obsolete + [ConditionalFact] + public virtual void Add_owned_reference_with_nested_collection_mapped_to_json_uses_empty_object_as_default_value() + => Execute( + _ => { }, + source => + { + source.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + }); + }, + target => + { + target.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + + e.OwnsOne( + "Owned", "json_reference", o => + { + o.ToJson(); + o.Property("Value"); + o.OwnsMany( + "Nested", "NestedCollection", n => + { + n.Property("Number"); + }); + }); + + e.Navigation("json_reference").IsRequired(); + }); + }, + upOps => + { + Assert.Equal(1, upOps.Count); + + var operation = Assert.IsType(upOps[0]); + Assert.Equal("Entity", operation.Table); + Assert.Equal("json_reference", operation.Name); + Assert.Equal("{}", operation.DefaultValue); + }, + downOps => + { + Assert.Equal(1, downOps.Count); + Assert.IsType(downOps[0]); + }); +#pragma warning restore EF8001 // Owned JSON entities are obsolete + [ConditionalFact] public virtual void Noop_on_complex_properties() => Execute( From 2e927720b50ca4760bea9db5921341b2276061cd Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sun, 22 Mar 2026 10:02:25 +0100 Subject: [PATCH 4/4] Fix IsJsonCollectionColumn for nested complex collections inside complex references was already applied to the owned entity branch. Without this, a nested ComplexCollection inside a ComplexProperty (reference) would falsely classify the root JSON column as a collection, producing '[]' instead of '{}'. Added regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Internal/MigrationsModelDiffer.cs | 3 +- .../Internal/MigrationsModelDifferTest.cs | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 37cfe9779c0..cb4f7f2868d 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -1288,7 +1288,8 @@ private static bool IsJsonCollectionColumn(JsonColumn jsonColumn) => jsonColumn.Table.ComplexTypeMappings.Any( m => m.TypeBase is IComplexType ct && ct.GetContainerColumnName() == jsonColumn.Name - && ct.ComplexProperty.IsCollection) + && ct.ComplexProperty.IsCollection + && !ct.ComplexProperty.DeclaringType.IsMappedToJson()) || jsonColumn.Table.EntityTypeMappings.Any( m => m.TypeBase is IEntityType et && et.GetContainerColumnName() == jsonColumn.Name diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 1395dcece3c..758128b0789 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -10053,6 +10053,54 @@ public virtual void Add_complex_collection_mapped_to_json_uses_empty_array_as_de Assert.IsType(downOps[0]); }); + [ConditionalFact] + public virtual void Add_complex_reference_with_nested_collection_mapped_to_json_uses_empty_object_as_default_value() + => Execute( + _ => { }, + source => + { + source.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + }); + }, + target => + { + target.Entity( + "Entity", e => + { + e.Property("Id").ValueGeneratedOnAdd(); + e.HasKey("Id"); + + e.ComplexProperty( + "ComplexReference", cp => + { + cp.IsRequired(); + cp.ToJson("json_reference"); + cp.Property(x => x.Value); + cp.Property(x => x.Date); + cp.ComplexCollection( + x => x.NestedCollection, nc => { }); + }); + }); + }, + upOps => + { + Assert.Equal(1, upOps.Count); + + var operation = Assert.IsType(upOps[0]); + Assert.Equal("Entity", operation.Table); + Assert.Equal("json_reference", operation.Name); + Assert.Equal("{}", operation.DefaultValue); + }, + downOps => + { + Assert.Equal(1, downOps.Count); + Assert.IsType(downOps[0]); + }); + #pragma warning disable EF8001 // Owned JSON entities are obsolete [ConditionalFact] public virtual void Add_owned_collection_mapped_to_json_has_nullable_column()