From 8272d1d3dec6ed3cb6d25ce434bb2e34b7715526 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 23 Oct 2025 23:13:47 +0000
Subject: [PATCH 1/3] Fix HasJsonPropertyName for complex properties
Fixes #37009
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../RelationalComplexPropertyExtensions.cs | 52 +++++++++++--
.../RelationalTypeBaseExtensions.cs | 19 +++--
.../RelationalModelBuilderTest.cs | 10 +++
.../Query/AdHocJsonQueryRelationalTestBase.cs | 73 +++++++++++++++++++
4 files changed, 141 insertions(+), 13 deletions(-)
diff --git a/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs
index e3f6e63b074..b8cd0ca6506 100644
--- a/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs
@@ -11,6 +11,9 @@ namespace Microsoft.EntityFrameworkCore;
///
public static class RelationalComplexPropertyExtensions
{
+ private static readonly bool UseOldBehavior37009 =
+ AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37009", out var enabled37009) && enabled37009;
+
///
/// Gets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
///
@@ -23,8 +26,15 @@ public static class RelationalComplexPropertyExtensions
/// is returned for complex properties of entities that are not mapped to a JSON column.
///
public static string? GetJsonPropertyName(this IReadOnlyComplexProperty complexProperty)
- => (string?)complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
- ?? (complexProperty.DeclaringType.IsMappedToJson() ? complexProperty.Name : null);
+ {
+ if (UseOldBehavior37009)
+ {
+ return (string?)complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
+ ?? (complexProperty.DeclaringType.IsMappedToJson() ? complexProperty.Name : null);
+ }
+
+ return complexProperty.ComplexType.GetJsonPropertyName();
+ }
///
/// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
@@ -32,9 +42,20 @@ public static class RelationalComplexPropertyExtensions
/// The complex property.
/// The name to be used.
public static void SetJsonPropertyName(this IMutableComplexProperty complexProperty, string? name)
- => complexProperty.SetOrRemoveAnnotation(
- RelationalAnnotationNames.JsonPropertyName,
- Check.NullButNotEmpty(name));
+ {
+ if (UseOldBehavior37009)
+ {
+ complexProperty.SetOrRemoveAnnotation(
+ RelationalAnnotationNames.JsonPropertyName,
+ Check.NullButNotEmpty(name));
+ }
+ else
+ {
+ complexProperty.ComplexType.SetOrRemoveAnnotation(
+ RelationalAnnotationNames.JsonPropertyName,
+ Check.NullButNotEmpty(name));
+ }
+ }
///
/// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
@@ -47,10 +68,20 @@ public static void SetJsonPropertyName(this IMutableComplexProperty complexPrope
this IConventionComplexProperty complexProperty,
string? name,
bool fromDataAnnotation = false)
- => (string?)complexProperty.SetOrRemoveAnnotation(
+ {
+ if (UseOldBehavior37009)
+ {
+ return (string?)complexProperty.SetOrRemoveAnnotation(
+ RelationalAnnotationNames.JsonPropertyName,
+ Check.NullButNotEmpty(name),
+ fromDataAnnotation)?.Value;
+ }
+
+ return (string?)complexProperty.ComplexType.SetOrRemoveAnnotation(
RelationalAnnotationNames.JsonPropertyName,
Check.NullButNotEmpty(name),
fromDataAnnotation)?.Value;
+ }
///
/// Gets the for the JSON property name for a given complex property.
@@ -58,5 +89,12 @@ public static void SetJsonPropertyName(this IMutableComplexProperty complexPrope
/// The complex property.
/// The for the JSON property name for a given complex property.
public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionComplexProperty complexProperty)
- => complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
+ {
+ if (UseOldBehavior37009)
+ {
+ return complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
+ }
+
+ return complexProperty.ComplexType.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
+ }
}
diff --git a/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs
index 0ce1ae6a31b..30c18bd86cf 100644
--- a/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs
@@ -464,12 +464,19 @@ public static void SetContainerColumnType(this IMutableTypeBase typeBase, string
/// is returned for entities that are not mapped to a JSON column.
///
public static string? GetJsonPropertyName(this IReadOnlyTypeBase typeBase)
- => (string?)typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
- ?? (!typeBase.IsMappedToJson()
- ? null
- : typeBase is IReadOnlyEntityType entityType
- ? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name
- : ((IReadOnlyComplexType)typeBase).ComplexProperty.Name);
+ {
+ var annotation = typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName);
+ if (annotation != null)
+ {
+ return (string?)annotation.Value;
+ }
+
+ return typeBase.FindAnnotation(RelationalAnnotationNames.ContainerColumnName) != null || !typeBase.IsMappedToJson()
+ ? null
+ : typeBase is IReadOnlyEntityType entityType
+ ? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name
+ : ((IReadOnlyComplexType)typeBase).ComplexProperty.Name;
+ }
#endregion
}
diff --git a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs
index 269a34c025e..fca2fceb55a 100644
--- a/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs
+++ b/test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs
@@ -914,6 +914,16 @@ public virtual void ComplexCollection_can_have_nested_complex_properties_mapped_
var nestedCol3 = complexCollectionProperty1.ComplexType.FindComplexProperty("Collection1")!;
Assert.Equal("CustomNestedCollection3", nestedCol3.GetJsonPropertyName());
+
+ // Verify that no complex properties have the JsonPropertyName annotation directly
+ foreach (var complexProperty in entityType.GetComplexProperties())
+ {
+ Assert.Null(complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName));
+ foreach (var nestedComplexProperty in complexProperty.ComplexType.GetComplexProperties())
+ {
+ Assert.Null(nestedComplexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName));
+ }
+ }
}
[ConditionalFact]
diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs
index 288aa8f373f..f99990292a3 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs
@@ -618,6 +618,79 @@ public class JsonEntity
#endregion
+ #region HasJsonPropertyName
+
+ [ConditionalFact]
+ public virtual async Task HasJsonPropertyName()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings),
+ onModelCreating: m => m.Entity().ComplexProperty(e => e.Json, b =>
+ {
+ b.ToJson();
+
+ b.Property(j => j.String).HasJsonPropertyName("string");
+
+ b.ComplexProperty(j => j.Nested, b =>
+ {
+ b.HasJsonPropertyName("nested");
+ b.Property(x => x.Int).HasJsonPropertyName("int");
+ });
+
+ b.ComplexCollection(a => a.NestedCollection, b =>
+ {
+ b.HasJsonPropertyName("nested_collection");
+ b.Property(x => x.Int).HasJsonPropertyName("int");
+ });
+ }),
+ seed: context =>
+ {
+ context.Set().Add(new Context37009.Entity
+ {
+ Json = new Context37009.JsonComplexType
+ {
+ String = "foo",
+ Nested = new Context37009.JsonNestedType { Int = 1 },
+ NestedCollection = [new Context37009.JsonNestedType { Int = 2 }]
+ }
+ });
+
+ return context.SaveChangesAsync();
+ });
+
+ await using var context = contextFactory.CreateContext();
+
+ Assert.Equal(1, await context.Set().CountAsync(e => e.Json.String == "foo"));
+ Assert.Equal(1, await context.Set().CountAsync(e => e.Json.Nested.Int == 1));
+ Assert.Equal(1, await context.Set().CountAsync(e => e.Json.NestedCollection.Any(x => x.Int == 2)));
+ }
+
+ protected class Context37009(DbContextOptions options) : DbContext(options)
+ {
+ public DbSet Entities { get; set; }
+
+ public class Entity
+ {
+ public int Id { get; set; }
+ public JsonComplexType Json { get; set; }
+ }
+
+ public class JsonComplexType
+ {
+ public string String { get; set; }
+
+ public JsonNestedType Nested { get; set; }
+ public List NestedCollection { get; set; }
+ }
+
+ public class JsonNestedType
+ {
+ public int Int { get; set; }
+ }
+ }
+
+ #endregion HasJsonPropertyName
+
protected TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;
From 2909659f30f853199debaedc14a9b3c93143a1f4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 24 Oct 2025 14:25:58 +0000
Subject: [PATCH 2/3] Add JsonPropertyName generation to ComplexType and remove
from ComplexProperty
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../Design/AnnotationCodeGenerator.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
index b3c1718ea06..1762c7e9f1c 100644
--- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
@@ -301,6 +301,11 @@ public virtual IReadOnlyList GenerateFluentApiCalls(
annotations.Remove(RelationalAnnotationNames.ContainerColumnType);
}
+ GenerateSimpleFluentApiCall(
+ annotations,
+ RelationalAnnotationNames.JsonPropertyName, nameof(RelationalComplexPropertyBuilderExtensions.HasJsonPropertyName),
+ methodCallCodeFragments);
+
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexType, annotations, GenerateFluentApi));
return methodCallCodeFragments;
@@ -397,11 +402,6 @@ public virtual IReadOnlyList GenerateFluentApiCalls(
{
var methodCallCodeFragments = new List();
- GenerateSimpleFluentApiCall(
- annotations,
- RelationalAnnotationNames.JsonPropertyName, nameof(RelationalComplexPropertyBuilderExtensions.HasJsonPropertyName),
- methodCallCodeFragments);
-
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexProperty, annotations, GenerateFluentApi));
return methodCallCodeFragments;
From 62d158254ee681b9cad559ce71afeb596ecfa453 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 24 Oct 2025 14:58:10 +0000
Subject: [PATCH 3/3] Restore JsonPropertyName generation in ComplexProperty
method
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
src/EFCore.Relational/Design/AnnotationCodeGenerator.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
index 1762c7e9f1c..05a71dd8bba 100644
--- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
+++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
@@ -402,6 +402,11 @@ public virtual IReadOnlyList GenerateFluentApiCalls(
{
var methodCallCodeFragments = new List();
+ GenerateSimpleFluentApiCall(
+ annotations,
+ RelationalAnnotationNames.JsonPropertyName, nameof(RelationalComplexPropertyBuilderExtensions.HasJsonPropertyName),
+ methodCallCodeFragments);
+
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexProperty, annotations, GenerateFluentApi));
return methodCallCodeFragments;