Skip to content

Commit

Permalink
[release/9.0] Change some JSON Metadata API to respect null values se…
Browse files Browse the repository at this point in the history
…t explicitly. (#34624)

Fixes #34434
  • Loading branch information
AndriySvyryd authored Sep 12, 2024
1 parent 8f4a0d6 commit 40a655c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 87 deletions.
10 changes: 7 additions & 3 deletions src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ public static void SetContainer(this IMutableEntityType entityType, string? name
/// <param name="entityType">The entity type to get the containing property name for.</param>
/// <returns>The name of the parent property to which the entity type is mapped.</returns>
public static string? GetContainingPropertyName(this IReadOnlyEntityType entityType)
=> entityType[CosmosAnnotationNames.PropertyName] as string
?? GetDefaultContainingPropertyName(entityType);
{
var propertyName = entityType.FindAnnotation(CosmosAnnotationNames.PropertyName);
return propertyName == null
? GetDefaultContainingPropertyName(entityType)
: (string?)propertyName.Value;
}

private static string? GetDefaultContainingPropertyName(IReadOnlyEntityType entityType)
=> entityType.FindOwnership() is IReadOnlyForeignKey ownership
Expand Down Expand Up @@ -198,7 +202,7 @@ public static void SetPartitionKeyPropertyName(this IMutableEntityType entityTyp
public static IReadOnlyList<string> GetPartitionKeyPropertyNames(this IReadOnlyEntityType entityType)
=> entityType[CosmosAnnotationNames.PartitionKeyNames] as IReadOnlyList<string>
?? entityType.BaseType?.GetPartitionKeyPropertyNames()
?? Array.Empty<string>();
?? [];

/// <summary>
/// Sets the names of the properties that are used to store the hierarchical partition key.
Expand Down
18 changes: 14 additions & 4 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1606,8 +1606,12 @@ public static void SetContainerColumnName(this IMutableEntityType entityType, st
/// <param name="entityType">The entity type to get the container column name for.</param>
/// <returns>The container column name to which the entity type is mapped.</returns>
public static string? GetContainerColumnName(this IReadOnlyEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value as string
?? entityType.FindOwnership()?.PrincipalEntityType.GetContainerColumnName();
{
var containerColumnName = entityType.FindAnnotation(RelationalAnnotationNames.ContainerColumnName);
return containerColumnName == null
? entityType.FindOwnership()?.PrincipalEntityType.GetContainerColumnName()
: (string?)containerColumnName.Value;
}

/// <summary>
/// Sets the column type to use for the container column to which the entity type is mapped.
Expand Down Expand Up @@ -1720,8 +1724,14 @@ public static void SetContainerColumnTypeMapping(this IMutableEntityType entityT
/// <see langword="null" /> is returned for entities that are not mapped to a JSON column.
/// </returns>
public static string? GetJsonPropertyName(this IReadOnlyEntityType entityType)
=> (string?)entityType.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
?? (!entityType.IsMappedToJson() ? null : entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name);
{
var propertyName = entityType.FindAnnotation(RelationalAnnotationNames.JsonPropertyName);
return propertyName == null
? (entityType.IsMappedToJson()
? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name
: null)
: (string?)propertyName.Value;
}

/// <summary>
/// Sets the value of JSON property name used for the given entity mapped to a JSON column.
Expand Down
13 changes: 12 additions & 1 deletion src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2833,8 +2833,13 @@ protected virtual void ValidateJsonEntityProperties(
IEntityType jsonEntityType)
{
var jsonPropertyNames = new List<string>();
foreach (var property in jsonEntityType.GetDeclaredProperties().Where(p => !string.IsNullOrEmpty(p.GetJsonPropertyName())))
foreach (var property in jsonEntityType.GetDeclaredProperties())
{
if (string.IsNullOrEmpty(property.GetJsonPropertyName()))
{
continue;
}

if (property.TryGetDefaultValue(out var _))
{
throw new InvalidOperationException(
Expand All @@ -2857,6 +2862,12 @@ protected virtual void ValidateJsonEntityProperties(

foreach (var navigation in jsonEntityType.GetDeclaredNavigations())
{
if (!navigation.TargetEntityType.IsMappedToJson()
|| navigation.IsOnDependent)
{
continue;
}

var jsonPropertyName = navigation.TargetEntityType.GetJsonPropertyName()!;
if (!jsonPropertyNames.Contains(jsonPropertyName))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1633,45 +1633,61 @@ public virtual void Json_entity_with_nested_structure_same_property_names()
x => x.OwnedCollection2, bb =>
{
bb.ToJson("col2");
bb.OwnsOne(x => x.Reference1);
bb.OwnsOne(x => x.Reference2);
bb.OwnsOne(x => x.Reference1)
.HasAnnotation(RelationalAnnotationNames.JsonPropertyName, null);
bb.OwnsOne(x => x.Reference2)
.ToTable("Ref2")
.HasAnnotation(RelationalAnnotationNames.ContainerColumnName, null);
bb.OwnsMany(x => x.Collection1);
bb.OwnsMany(x => x.Collection2);
});
});

var model = modelBuilder.FinalizeModel();
var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel));
Assert.Equal(4, outerOwnedEntities.Count());

Assert.Collection(outerOwnedEntities,
e => Assert.Equal("col1", e.GetContainerColumnName()),
e => Assert.Equal("col2", e.GetContainerColumnName()),
e => Assert.Equal("ref1", e.GetContainerColumnName()),
e => Assert.Equal("ref2", e.GetContainerColumnName()));

foreach (var outerOwnedEntity in outerOwnedEntities)
{
Assert.Equal("Date", outerOwnedEntity.GetProperty("Date").GetJsonPropertyName());
Assert.Equal("Fraction", outerOwnedEntity.GetProperty("Fraction").GetJsonPropertyName());
Assert.Equal("Enum", outerOwnedEntity.GetProperty("Enum").GetJsonPropertyName());
Assert.Equal(
"Reference1",
outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference1").TargetEntityType.GetJsonPropertyName());
Assert.Equal(
"Reference2",
outerOwnedEntity.GetNavigations().Single(n => n.Name == "Reference2").TargetEntityType.GetJsonPropertyName());
Assert.Equal(
"Collection1",
outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection1").TargetEntityType.GetJsonPropertyName());
Assert.Equal(
"Collection2",
outerOwnedEntity.GetNavigations().Single(n => n.Name == "Collection2").TargetEntityType.GetJsonPropertyName());
}

var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity));
Assert.Equal(16, ownedEntities.Count());
var nestedOwnedTypes = outerOwnedEntity.GetNavigations().Select(n => n.TargetEntityType).ToList();
Assert.Collection(nestedOwnedTypes,
e => Assert.Equal("Collection1", e.GetJsonPropertyName()),
e => Assert.Equal("Collection2", e.GetJsonPropertyName()),
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName() == "col2" ? null : "Reference1",
e.GetJsonPropertyName()),
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName() == "col2" ? null : "Reference2",
e.GetJsonPropertyName()));

Assert.Collection(nestedOwnedTypes,
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()),
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()),
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()),
e => Assert.Equal(outerOwnedEntity.GetContainerColumnName() == "col2" ?
null : outerOwnedEntity.GetContainerColumnName(), e.GetContainerColumnName()));

foreach (var ownedEntity in nestedOwnedTypes)
{
if (ownedEntity.GetContainerColumnName() == null)
{
continue;
}

foreach (var ownedEntity in ownedEntities)
{
Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName());
Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName());
Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName());
Assert.Equal("Date", ownedEntity.GetProperty("Date").GetJsonPropertyName());
Assert.Equal("Fraction", ownedEntity.GetProperty("Fraction").GetJsonPropertyName());
Assert.Equal("Enum", ownedEntity.GetProperty("Enum").GetJsonPropertyName());
}
}

Assert.Equal(16, model.FindEntityTypes(typeof(OwnedEntity)).Count());
}

[ConditionalFact]
Expand Down Expand Up @@ -2047,62 +2063,6 @@ public virtual void Json_entity_and_normal_owned_can_exist_side_to_side_on_same_
Assert.Equal(2, ownedEntities.Where(e => e.IsMappedToJson()).Count());
Assert.Equal(2, ownedEntities.Where(e => e.IsOwned() && !e.IsMappedToJson()).Count());
}

[ConditionalFact]
public virtual void Json_entity_with_nested_structure_same_property_names_()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<JsonEntityWithNesting>(
b =>
{
b.OwnsOne(
x => x.OwnedReference1, bb =>
{
bb.ToJson("ref1");
bb.OwnsOne(x => x.Reference1);
bb.OwnsOne(x => x.Reference2);
bb.OwnsMany(x => x.Collection1);
bb.OwnsMany(x => x.Collection2);
});
b.OwnsOne(
x => x.OwnedReference2, bb =>
{
bb.ToJson("ref2");
bb.OwnsOne(x => x.Reference1);
bb.OwnsOne(x => x.Reference2);
bb.OwnsMany(x => x.Collection1);
bb.OwnsMany(x => x.Collection2);
});
b.OwnsMany(
x => x.OwnedCollection1, bb =>
{
bb.ToJson("col1");
bb.OwnsOne(x => x.Reference1);
bb.OwnsOne(x => x.Reference2);
bb.OwnsMany(x => x.Collection1);
bb.OwnsMany(x => x.Collection2);
});
b.OwnsMany(
x => x.OwnedCollection2, bb =>
{
bb.ToJson("col2");
bb.OwnsOne(x => x.Reference1);
bb.OwnsOne(x => x.Reference2);
bb.OwnsMany(x => x.Collection1);
bb.OwnsMany(x => x.Collection2);
});
});

var model = modelBuilder.FinalizeModel();
var outerOwnedEntities = model.FindEntityTypes(typeof(OwnedEntityExtraLevel));
Assert.Equal(4, outerOwnedEntities.Count());

var ownedEntities = model.FindEntityTypes(typeof(OwnedEntity));
Assert.Equal(16, ownedEntities.Count());
}
}

public class SqlServerModelBuilderFixture : RelationalModelBuilderFixture
Expand Down

0 comments on commit 40a655c

Please sign in to comment.