From dec296eca2b20391cf0c8431e6245ba0d160bb7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 02:22:08 +0000 Subject: [PATCH 1/5] Initial plan From fadc7bf3221d9cc73adb28834bef4f70e6b8b40c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 02:37:14 +0000 Subject: [PATCH 2/5] Fix complex property JSON column not marked nullable in TPH hierarchy Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Metadata/Internal/RelationalModel.cs | 10 +++++- .../Metadata/RelationalModelTest.cs | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 9f6c26fb8fb..34dd08cf15c 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -634,8 +634,16 @@ private static void CreateContainerColumn( { complexType = (IComplexType)mappedType; #pragma warning disable EF1001 // Internal EF Core API usage. + var chain = complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true); jsonColumn.IsNullable = complexType.ComplexProperty.IsNullable - || complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true).Any(p => p.IsNullable); + || chain.Any(p => p.IsNullable); + + if (chain[0].DeclaringType is IEntityType declaringEntityType + && declaringEntityType.BaseType != null) + { + // if the complex property is defined on a derived type, the column must be made nullable + jsonColumn.IsNullable = true; + } #pragma warning restore EF1001 // Internal EF Core API usage. } } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index cff152fe567..ef756bd56ed 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -3201,6 +3201,26 @@ public void Container_column_type_is_used_for_complex_collection_json_column() Assert.IsType(jsonColumn); } + [ConditionalFact] + public void Complex_property_json_column_is_nullable_in_TPH_hierarchy() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity() + .ComplexProperty(e => e.ComplexProperty, b => b.ToJson()); + + var model = modelBuilder.FinalizeModel(); + var relationalModel = model.GetRelationalModel(); + + var table = relationalModel.Tables.Single(); + var jsonColumn = table.Columns.Single(c => c.Name == "ComplexProperty"); + + Assert.True(jsonColumn.IsNullable); + Assert.IsType(jsonColumn); + } + private static IRelationalModel Finalize(TestHelpers.TestModelBuilder modelBuilder) => modelBuilder.FinalizeModel(designTime: true).GetRelationalModel(); @@ -3318,6 +3338,18 @@ private class EntityWithComplexCollection public List ComplexCollection { get; set; } } + private abstract class TphBaseEntity + { + public int Id { get; set; } + } + + private class EntityWithoutComplexProperty : TphBaseEntity; + + private class TphEntityWithComplexProperty : TphBaseEntity + { + public ComplexData ComplexProperty { get; set; } + } + private class ComplexData { public string Value { get; set; } From 051f1515c5e23d5801172d8b00a0b7e6848e8e82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:35:36 +0000 Subject: [PATCH 3/5] Combine IsNullable conditions for complex property JSON column Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Metadata/Internal/RelationalModel.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 34dd08cf15c..108f283fecd 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -636,14 +636,8 @@ private static void CreateContainerColumn( #pragma warning disable EF1001 // Internal EF Core API usage. var chain = complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true); jsonColumn.IsNullable = complexType.ComplexProperty.IsNullable - || chain.Any(p => p.IsNullable); - - if (chain[0].DeclaringType is IEntityType declaringEntityType - && declaringEntityType.BaseType != null) - { - // if the complex property is defined on a derived type, the column must be made nullable - jsonColumn.IsNullable = true; - } + || chain.Any(p => p.IsNullable) + || (chain[0].DeclaringType is IEntityType declaringEntityType && declaringEntityType.BaseType != null); #pragma warning restore EF1001 // Internal EF Core API usage. } } From e3cc782738af81a3f2e9e19ba22fcafa65270cf5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:47:33 +0000 Subject: [PATCH 4/5] Move TPH check before chain.Any and add explicit TPH mapping strategy check Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore.Relational/Metadata/Internal/RelationalModel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 108f283fecd..1df7270a58f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -636,8 +636,11 @@ private static void CreateContainerColumn( #pragma warning disable EF1001 // Internal EF Core API usage. var chain = complexType.ComplexProperty.GetChainToComplexProperty(fromEntity: true); jsonColumn.IsNullable = complexType.ComplexProperty.IsNullable - || chain.Any(p => p.IsNullable) - || (chain[0].DeclaringType is IEntityType declaringEntityType && declaringEntityType.BaseType != null); + || (chain[0].DeclaringType is IEntityType declaringEntityType + && declaringEntityType.BaseType != null + && (declaringEntityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy) + == RelationalAnnotationNames.TphMappingStrategy) + || chain.Any(p => p.IsNullable); #pragma warning restore EF1001 // Internal EF Core API usage. } } From d908ec62a34720ac6a0bb5945a966f5569d28629 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 05:20:08 +0000 Subject: [PATCH 5/5] Update ComplexTypes baseline: mark ManyOwned JSON column as nullable in TPH hierarchy Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Baselines/ComplexTypes/DbContextModelBuilder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs index e7e1b974016..4b02f75417a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/ComplexTypes/DbContextModelBuilder.cs @@ -406,7 +406,10 @@ private IRelationalModel CreateRelationalModel() var flagsEnum2Column = new Column("FlagsEnum2", "int", principalBaseTable); principalBaseTable.Columns.Add("FlagsEnum2", flagsEnum2Column); flagsEnum2Column.Accessors = ColumnAccessorsFactory.CreateGeneric(flagsEnum2Column); - var manyOwnedColumn = new JsonColumn("ManyOwned", "nvarchar(max)", principalBaseTable); + var manyOwnedColumn = new JsonColumn("ManyOwned", "nvarchar(max)", principalBaseTable) + { + IsNullable = true + }; principalBaseTable.Columns.Add("ManyOwned", manyOwnedColumn); manyOwnedColumn.Accessors = ColumnAccessorsFactory.CreateGeneric(manyOwnedColumn); var owned_NumberColumn = new Column("Owned_Number", "int", principalBaseTable);