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; diff --git a/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs index e3f6e63b074..4c17d16ab1b 100644 --- a/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalComplexPropertyExtensions.cs @@ -23,8 +23,7 @@ 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); + => 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 +31,7 @@ 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)); + => complexProperty.ComplexType.SetJsonPropertyName(name); /// /// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column. @@ -47,10 +44,7 @@ public static void SetJsonPropertyName(this IMutableComplexProperty complexPrope this IConventionComplexProperty complexProperty, string? name, bool fromDataAnnotation = false) - => (string?)complexProperty.SetOrRemoveAnnotation( - RelationalAnnotationNames.JsonPropertyName, - Check.NullButNotEmpty(name), - fromDataAnnotation)?.Value; + => complexProperty.ComplexType.SetJsonPropertyName(name, fromDataAnnotation); /// /// Gets the for the JSON property name for a given complex property. @@ -58,5 +52,5 @@ 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(); + => complexProperty.ComplexType.GetJsonPropertyNameConfigurationSource(); } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 0f0833b02bc..d32e1d4deb5 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -1620,10 +1620,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT this IConventionEntityType entityType, string? name, bool fromDataAnnotation = false) - => (string?)entityType.SetOrRemoveAnnotation( - RelationalAnnotationNames.JsonPropertyName, - Check.NullButNotEmpty(name), - fromDataAnnotation)?.Value; + => ((IConventionTypeBase)entityType).SetJsonPropertyName(name, fromDataAnnotation); /// /// Gets the value of JSON property name used for the given entity mapped to a JSON column. @@ -1652,9 +1649,7 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT /// The entity type. /// The name to be used. public static void SetJsonPropertyName(this IMutableEntityType entityType, string? name) - => entityType.SetOrRemoveAnnotation( - RelationalAnnotationNames.JsonPropertyName, - Check.NullButNotEmpty(name)); + => ((IMutableTypeBase)entityType).SetJsonPropertyName(name); /// /// Gets the for the JSON property name for a given entity type. @@ -1662,7 +1657,7 @@ public static void SetJsonPropertyName(this IMutableEntityType entityType, strin /// The entity type. /// The for the JSON property name for a given entity type. public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionEntityType entityType) - => entityType.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource(); + => ((IConventionTypeBase)entityType).GetJsonPropertyNameConfigurationSource(); #endregion } diff --git a/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs index 0ce1ae6a31b..b5ee748ab77 100644 --- a/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs @@ -464,12 +464,53 @@ 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; + } + + /// + /// Sets the value of JSON property name used for the given type mapped to a JSON column. + /// + /// The type. + /// The name to be used. + public static void SetJsonPropertyName(this IMutableTypeBase typeBase, string? name) + => typeBase.SetOrRemoveAnnotation( + RelationalAnnotationNames.JsonPropertyName, + Check.NullButNotEmpty(name)); + + /// + /// Sets the value of JSON property name used for the given type mapped to a JSON column. + /// + /// The type. + /// The name to be used. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static string? SetJsonPropertyName( + this IConventionTypeBase typeBase, + string? name, + bool fromDataAnnotation = false) + => (string?)typeBase.SetOrRemoveAnnotation( + RelationalAnnotationNames.JsonPropertyName, + Check.NullButNotEmpty(name), + fromDataAnnotation)?.Value; + + /// + /// Gets the for the JSON property name for a given type. + /// + /// The type. + /// The for the JSON property name for a given type. + public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionTypeBase typeBase) + => typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource(); #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;