Skip to content

Commit 25482a3

Browse files
[release/10.0] Fix HasJsonPropertyName for complex properties (#37020)
Fixes #37009 Co-authored-by: AndriySvyryd <[email protected]>
1 parent f43164b commit 25482a3

File tree

5 files changed

+146
-13
lines changed

5 files changed

+146
-13
lines changed

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,11 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
301301
annotations.Remove(RelationalAnnotationNames.ContainerColumnType);
302302
}
303303

304+
GenerateSimpleFluentApiCall(
305+
annotations,
306+
RelationalAnnotationNames.JsonPropertyName, nameof(RelationalComplexPropertyBuilderExtensions.HasJsonPropertyName),
307+
methodCallCodeFragments);
308+
304309
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(complexType, annotations, GenerateFluentApi));
305310

306311
return methodCallCodeFragments;

src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ namespace Microsoft.EntityFrameworkCore;
1111
/// </remarks>
1212
public static class RelationalComplexPropertyExtensions
1313
{
14+
private static readonly bool UseOldBehavior37009 =
15+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37009", out var enabled37009) && enabled37009;
16+
1417
/// <summary>
1518
/// Gets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
1619
/// </summary>
@@ -23,18 +26,36 @@ public static class RelationalComplexPropertyExtensions
2326
/// <see langword="null" /> is returned for complex properties of entities that are not mapped to a JSON column.
2427
/// </returns>
2528
public static string? GetJsonPropertyName(this IReadOnlyComplexProperty complexProperty)
26-
=> (string?)complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
27-
?? (complexProperty.DeclaringType.IsMappedToJson() ? complexProperty.Name : null);
29+
{
30+
if (UseOldBehavior37009)
31+
{
32+
return (string?)complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
33+
?? (complexProperty.DeclaringType.IsMappedToJson() ? complexProperty.Name : null);
34+
}
35+
36+
return complexProperty.ComplexType.GetJsonPropertyName();
37+
}
2838

2939
/// <summary>
3040
/// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
3141
/// </summary>
3242
/// <param name="complexProperty">The complex property.</param>
3343
/// <param name="name">The name to be used.</param>
3444
public static void SetJsonPropertyName(this IMutableComplexProperty complexProperty, string? name)
35-
=> complexProperty.SetOrRemoveAnnotation(
36-
RelationalAnnotationNames.JsonPropertyName,
37-
Check.NullButNotEmpty(name));
45+
{
46+
if (UseOldBehavior37009)
47+
{
48+
complexProperty.SetOrRemoveAnnotation(
49+
RelationalAnnotationNames.JsonPropertyName,
50+
Check.NullButNotEmpty(name));
51+
}
52+
else
53+
{
54+
complexProperty.ComplexType.SetOrRemoveAnnotation(
55+
RelationalAnnotationNames.JsonPropertyName,
56+
Check.NullButNotEmpty(name));
57+
}
58+
}
3859

3960
/// <summary>
4061
/// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
@@ -47,16 +68,33 @@ public static void SetJsonPropertyName(this IMutableComplexProperty complexPrope
4768
this IConventionComplexProperty complexProperty,
4869
string? name,
4970
bool fromDataAnnotation = false)
50-
=> (string?)complexProperty.SetOrRemoveAnnotation(
71+
{
72+
if (UseOldBehavior37009)
73+
{
74+
return (string?)complexProperty.SetOrRemoveAnnotation(
75+
RelationalAnnotationNames.JsonPropertyName,
76+
Check.NullButNotEmpty(name),
77+
fromDataAnnotation)?.Value;
78+
}
79+
80+
return (string?)complexProperty.ComplexType.SetOrRemoveAnnotation(
5181
RelationalAnnotationNames.JsonPropertyName,
5282
Check.NullButNotEmpty(name),
5383
fromDataAnnotation)?.Value;
84+
}
5485

5586
/// <summary>
5687
/// Gets the <see cref="ConfigurationSource" /> for the JSON property name for a given complex property.
5788
/// </summary>
5889
/// <param name="complexProperty">The complex property.</param>
5990
/// <returns>The <see cref="ConfigurationSource" /> for the JSON property name for a given complex property.</returns>
6091
public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionComplexProperty complexProperty)
61-
=> complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
92+
{
93+
if (UseOldBehavior37009)
94+
{
95+
return complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
96+
}
97+
98+
return complexProperty.ComplexType.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
99+
}
62100
}

src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,19 @@ public static void SetContainerColumnType(this IMutableTypeBase typeBase, string
464464
/// <see langword="null" /> is returned for entities that are not mapped to a JSON column.
465465
/// </returns>
466466
public static string? GetJsonPropertyName(this IReadOnlyTypeBase typeBase)
467-
=> (string?)typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
468-
?? (!typeBase.IsMappedToJson()
469-
? null
470-
: typeBase is IReadOnlyEntityType entityType
471-
? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name
472-
: ((IReadOnlyComplexType)typeBase).ComplexProperty.Name);
467+
{
468+
var annotation = typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName);
469+
if (annotation != null)
470+
{
471+
return (string?)annotation.Value;
472+
}
473+
474+
return typeBase.FindAnnotation(RelationalAnnotationNames.ContainerColumnName) != null || !typeBase.IsMappedToJson()
475+
? null
476+
: typeBase is IReadOnlyEntityType entityType
477+
? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name
478+
: ((IReadOnlyComplexType)typeBase).ComplexProperty.Name;
479+
}
473480

474481
#endregion
475482
}

test/EFCore.Relational.Specification.Tests/ModelBuilding/RelationalModelBuilderTest.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,16 @@ public virtual void ComplexCollection_can_have_nested_complex_properties_mapped_
914914

915915
var nestedCol3 = complexCollectionProperty1.ComplexType.FindComplexProperty("Collection1")!;
916916
Assert.Equal("CustomNestedCollection3", nestedCol3.GetJsonPropertyName());
917+
918+
// Verify that no complex properties have the JsonPropertyName annotation directly
919+
foreach (var complexProperty in entityType.GetComplexProperties())
920+
{
921+
Assert.Null(complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName));
922+
foreach (var nestedComplexProperty in complexProperty.ComplexType.GetComplexProperties())
923+
{
924+
Assert.Null(nestedComplexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName));
925+
}
926+
}
917927
}
918928

919929
[ConditionalFact]

test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryRelationalTestBase.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,79 @@ public class JsonEntity
618618

619619
#endregion
620620

621+
#region HasJsonPropertyName
622+
623+
[ConditionalFact]
624+
public virtual async Task HasJsonPropertyName()
625+
{
626+
var contextFactory = await InitializeAsync<Context37009>(
627+
onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings),
628+
onModelCreating: m => m.Entity<Context37009.Entity>().ComplexProperty(e => e.Json, b =>
629+
{
630+
b.ToJson();
631+
632+
b.Property(j => j.String).HasJsonPropertyName("string");
633+
634+
b.ComplexProperty(j => j.Nested, b =>
635+
{
636+
b.HasJsonPropertyName("nested");
637+
b.Property(x => x.Int).HasJsonPropertyName("int");
638+
});
639+
640+
b.ComplexCollection(a => a.NestedCollection, b =>
641+
{
642+
b.HasJsonPropertyName("nested_collection");
643+
b.Property(x => x.Int).HasJsonPropertyName("int");
644+
});
645+
}),
646+
seed: context =>
647+
{
648+
context.Set<Context37009.Entity>().Add(new Context37009.Entity
649+
{
650+
Json = new Context37009.JsonComplexType
651+
{
652+
String = "foo",
653+
Nested = new Context37009.JsonNestedType { Int = 1 },
654+
NestedCollection = [new Context37009.JsonNestedType { Int = 2 }]
655+
}
656+
});
657+
658+
return context.SaveChangesAsync();
659+
});
660+
661+
await using var context = contextFactory.CreateContext();
662+
663+
Assert.Equal(1, await context.Set<Context37009.Entity>().CountAsync(e => e.Json.String == "foo"));
664+
Assert.Equal(1, await context.Set<Context37009.Entity>().CountAsync(e => e.Json.Nested.Int == 1));
665+
Assert.Equal(1, await context.Set<Context37009.Entity>().CountAsync(e => e.Json.NestedCollection.Any(x => x.Int == 2)));
666+
}
667+
668+
protected class Context37009(DbContextOptions options) : DbContext(options)
669+
{
670+
public DbSet<Entity> Entities { get; set; }
671+
672+
public class Entity
673+
{
674+
public int Id { get; set; }
675+
public JsonComplexType Json { get; set; }
676+
}
677+
678+
public class JsonComplexType
679+
{
680+
public string String { get; set; }
681+
682+
public JsonNestedType Nested { get; set; }
683+
public List<JsonNestedType> NestedCollection { get; set; }
684+
}
685+
686+
public class JsonNestedType
687+
{
688+
public int Int { get; set; }
689+
}
690+
}
691+
692+
#endregion HasJsonPropertyName
693+
621694
protected TestSqlLoggerFactory TestSqlLoggerFactory
622695
=> (TestSqlLoggerFactory)ListLoggerFactory;
623696

0 commit comments

Comments
 (0)