Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static class RelationalComplexPropertyExtensions
/// </returns>
public static string? GetJsonPropertyName(this IReadOnlyComplexProperty complexProperty)
=> (string?)complexProperty.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
?? (!complexProperty.DeclaringType.IsMappedToJson() ? null : complexProperty.Name);
?? (complexProperty.DeclaringType.IsMappedToJson() ? complexProperty.Name : null);

/// <summary>
/// Sets the value of JSON property name used for the given complex property of an entity mapped to a JSON column.
Expand Down
44 changes: 22 additions & 22 deletions src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -542,31 +542,31 @@ private static void CreateTableMapping(

CreateColumnMapping(column, property, tableMapping);
}
}

// TODO: Change this to call GetComplexProperties()
// Issue #31248
foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
{
var complexType = complexProperty.ComplexType;

var complexTableMappings =
(List<TableMapping>?)complexType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings);
if (complexTableMappings == null)
{
complexTableMappings = [];
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, complexTableMappings);
}
// TODO: Change this to call GetComplexProperties()
// Issue #31248
foreach (var complexProperty in mappedType.GetDeclaredComplexProperties())
{
var complexType = complexProperty.ComplexType;

CreateTableMapping(
relationalTypeMappingSource,
complexType,
complexType,
mappedTable,
databaseModel,
complexTableMappings,
includesDerivedTypes: true,
isSplitEntityTypePrincipal: isSplitEntityTypePrincipal == true ? false : isSplitEntityTypePrincipal);
var complexTableMappings =
(List<TableMapping>?)complexType.FindRuntimeAnnotationValue(RelationalAnnotationNames.TableMappings);
if (complexTableMappings == null)
{
complexTableMappings = [];
complexType.AddRuntimeAnnotation(RelationalAnnotationNames.TableMappings, complexTableMappings);
}

CreateTableMapping(
relationalTypeMappingSource,
complexType,
complexType,
mappedTable,
databaseModel,
complexTableMappings,
includesDerivedTypes: true,
isSplitEntityTypePrincipal: isSplitEntityTypePrincipal == true ? false : isSplitEntityTypePrincipal);
}

if (((ITableMappingBase)tableMapping).ColumnMappings.Any()
Expand Down
145 changes: 103 additions & 42 deletions src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using IColumnMapping = Microsoft.EntityFrameworkCore.Metadata.IColumnMapping;
using ITableMapping = Microsoft.EntityFrameworkCore.Metadata.ITableMapping;
Expand Down Expand Up @@ -243,7 +244,7 @@ private sealed class JsonPartialUpdateInfo
public object? PropertyValue { get; set; }
}

private record struct JsonPartialUpdatePathEntry(string PropertyName, int? Ordinal, IUpdateEntry ParentEntry, INavigation Navigation);
private record struct JsonPartialUpdatePathEntry(string PropertyName, int? Ordinal, IUpdateEntry ParentEntry, INavigation? Navigation, IComplexProperty? ComplexProperty = null);

private List<IColumnModification> GenerateColumnModifications()
{
Expand All @@ -263,7 +264,7 @@ private List<IColumnModification> GenerateColumnModifications()
{
Check.DebugAssert(StoreStoredProcedure is null, "Multiple entries/shared identity not supported with stored procedures");

sharedTableColumnMap = new Dictionary<string, ColumnValuePropagator>();
sharedTableColumnMap = [];

if (_comparer != null
&& _entries.Count > 1)
Expand Down Expand Up @@ -297,6 +298,7 @@ private List<IColumnModification> GenerateColumnModifications()
if (!jsonEntry)
{
if (entry.EntityType.IsMappedToJson()
|| entry.EntityType.GetFlattenedComplexProperties().Any(cp => cp.ComplexType.IsMappedToJson())
|| entry.EntityType.GetNavigations().Any(e => e.IsCollection && e.TargetEntityType.IsMappedToJson()))
{
jsonEntry = true;
Expand Down Expand Up @@ -654,7 +656,8 @@ static JsonPartialUpdateInfo FindCommonJsonPartialUpdateInfo(
first.Path[i].PropertyName,
null,
first.Path[i].ParentEntry,
first.Path[i].Navigation);
Navigation: first.Path[i].Navigation,
ComplexProperty: first.Path[i].ComplexProperty);

result.Path.Add(common);

Expand All @@ -671,11 +674,15 @@ void HandleJson(List<IColumnModification> columnModifications)
{
var jsonColumnsUpdateMap = new Dictionary<IColumn, JsonPartialUpdateInfo>();
var processedEntries = new List<IUpdateEntry>();
foreach (var entry in _entries.Where(e => e.EntityType.IsMappedToJson()))
foreach (var entry in _entries)
{
if (!entry.EntityType.IsMappedToJson())
{
continue;
}

var jsonColumn = GetTableMapping(entry.EntityType)!.Table.FindColumn(entry.EntityType.GetContainerColumnName()!)!;
var jsonPartialUpdateInfo = FindJsonPartialUpdateInfo(entry, processedEntries);

if (jsonPartialUpdateInfo == null)
{
continue;
Expand All @@ -691,8 +698,13 @@ void HandleJson(List<IColumnModification> columnModifications)
jsonColumnsUpdateMap[jsonColumn] = jsonPartialUpdateInfo;
}

foreach (var entry in _entries.Where(e => !e.EntityType.IsMappedToJson()))
foreach (var entry in _entries)
{
if (entry.EntityType.IsMappedToJson())
{
continue;
}

foreach (var jsonCollectionNavigation in entry.EntityType.GetNavigations()
.Where(
n => n.IsCollection
Expand All @@ -712,14 +724,32 @@ void HandleJson(List<IColumnModification> columnModifications)
jsonColumnsUpdateMap[jsonCollectionColumn] = jsonPartialUpdateInfo;
}
}

foreach (var complexProperty in entry.EntityType.GetFlattenedComplexProperties()
.Where(cp => cp.ComplexType.IsMappedToJson() && !cp.DeclaringType.IsMappedToJson()))
{
var complexType = complexProperty.ComplexType;
var jsonColumn = GetTableMapping(entry.EntityType)!.Table.FindColumn(complexType.GetContainerColumnName()!)!;

if (!jsonColumnsUpdateMap.ContainsKey(jsonColumn))
{
var jsonPartialUpdateInfo = new JsonPartialUpdateInfo();
jsonPartialUpdateInfo.Path.Insert(0, new JsonPartialUpdatePathEntry("$", null, entry, Navigation: null, ComplexProperty: complexProperty));
jsonPartialUpdateInfo.PropertyValue = entry.GetCurrentValue(complexProperty);
jsonColumnsUpdateMap[jsonColumn] = jsonPartialUpdateInfo;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the full context, but it seems odd that we populate this map and then iterate over it to process it - can we simply extract the code below to a local function and call that instead of having the intermediate map? I guess we'd still need a bit of tracking to ensure we don't handle the same JSON column (though I'm assuming mapping two navigations/complex properties to the same JSON column isn't supported?).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, all this refactoring will come later

}
}
}

foreach (var (jsonColumn, updateInfo) in jsonColumnsUpdateMap)
{
var finalUpdatePathElement = updateInfo.Path.Last();
var navigation = finalUpdatePathElement.Navigation;
var complexProperty = finalUpdatePathElement.ComplexProperty;
var jsonColumnTypeMapping = jsonColumn.StoreTypeMapping;
var navigationValue = finalUpdatePathElement.ParentEntry.GetCurrentValue(navigation);
var jsonContainerProperty = (IPropertyBase?)navigation ?? complexProperty;
var navigationValue = finalUpdatePathElement.ParentEntry.GetCurrentValue(jsonContainerProperty!);

var jsonPathString = string.Join(
".", updateInfo.Path.Select(x => x.PropertyName + (x.Ordinal != null ? "[" + x.Ordinal + "]" : "")));
if (updateInfo.Property is IProperty property)
Expand Down Expand Up @@ -755,8 +785,8 @@ void HandleJson(List<IColumnModification> columnModifications)
WriteJson(
writer,
navigationValueElement,
finalUpdatePathElement.ParentEntry,
navigation.TargetEntityType,
(IInternalEntry)finalUpdatePathElement.ParentEntry,
((IPropertyBase?)navigation) ?? complexProperty!,
ordinal: null,
isCollection: false,
isTopLevel: true);
Expand All @@ -769,13 +799,14 @@ void HandleJson(List<IColumnModification> columnModifications)
}
else
{
var propertyBase = ((IPropertyBase?)navigation) ?? complexProperty!;
WriteJson(
writer,
navigationValue,
finalUpdatePathElement.ParentEntry,
navigation.TargetEntityType,
(IInternalEntry)finalUpdatePathElement.ParentEntry,
((IPropertyBase?)navigation) ?? complexProperty!,
ordinal: null,
isCollection: navigation.IsCollection,
isCollection: propertyBase.IsCollection,
isTopLevel: true);
}

Expand Down Expand Up @@ -840,16 +871,20 @@ protected virtual void ProcessSinglePropertyJsonUpdate(ref ColumnModificationPar
}
}

#pragma warning disable EF1001 // Internal EF Core API usage.
private void WriteJson(
Utf8JsonWriter writer,
object? navigationValue,
IUpdateEntry parentEntry,
IEntityType entityType,
object? value,
IInternalEntry parentEntry,
IPropertyBase property,
int? ordinal,
bool isCollection,
bool isTopLevel)
{
if (navigationValue == null)
var structuralType = property is INavigation navigation
? (ITypeBase)navigation.TargetEntityType
: ((IComplexProperty)property).ComplexType;
if (value is null)
{
if (!isTopLevel)
{
Expand All @@ -861,15 +896,15 @@ private void WriteJson(

if (isCollection)
{
var i = 1;
var i = 0;
writer.WriteStartArray();
foreach (var collectionElement in (IEnumerable)navigationValue)
foreach (var collectionElement in (IEnumerable)value)
{
WriteJson(
writer,
collectionElement,
parentEntry,
entityType,
property,
i++,
isCollection: false,
isTopLevel: false);
Expand All @@ -879,63 +914,89 @@ private void WriteJson(
return;
}

#pragma warning disable EF1001 // Internal EF Core API usage.
var entry = (IUpdateEntry)((InternalEntityEntry)parentEntry).StateManager.TryGetEntry(navigationValue, entityType)!;
#pragma warning restore EF1001 // Internal EF Core API usage.

writer.WriteStartObject();
foreach (var property in entityType.GetFlattenedProperties())

var entry = structuralType is IComplexType complexType
? complexType.ComplexProperty.IsCollection
? parentEntry.GetComplexCollectionEntry(complexType.ComplexProperty, ordinal!.Value)
: parentEntry
: ((InternalEntityEntry)parentEntry).StateManager.TryGetEntry(value, (IEntityType)structuralType)!;
WriteJsonObject(writer, parentEntry, entry, structuralType, ordinal);

writer.WriteEndObject();
}

private void WriteJsonObject(Utf8JsonWriter writer, IInternalEntry parentEntry, IInternalEntry entry, ITypeBase structuralType, int? ordinal)
{
foreach (var property in structuralType.GetProperties())
{
if (property.IsKey())
{
if (property.IsOrdinalKeyProperty() && ordinal != null)
{
entry.SetStoreGeneratedValue(property, ordinal.Value, setModified: false);
entry.SetStoreGeneratedValue(property, ordinal.Value + 1, setModified: false);
}

continue;
}

// jsonPropertyName can only be null for key properties
var jsonPropertyName = property.GetJsonPropertyName()!;
var value = entry.GetCurrentValue(property);
var propertyValue = entry.GetCurrentValue(property);
writer.WritePropertyName(jsonPropertyName);

if (value is not null)
if (propertyValue is not null)
{
var jsonValueReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter;
Check.DebugAssert(jsonValueReaderWriter is not null, "Missing JsonValueReaderWriter on JSON property");
jsonValueReaderWriter.ToJson(writer, value);
jsonValueReaderWriter.ToJson(writer, propertyValue);
}
else
{
writer.WriteNullValue();
}
}

foreach (var navigation in entityType.GetNavigations())
foreach (var complexProperty in structuralType.GetComplexProperties())
{
// skip back-references to the parent
if (navigation.IsOnDependent)
{
continue;
}

var jsonPropertyName = navigation.TargetEntityType.GetJsonPropertyName()!;
var ownedNavigationValue = entry.GetCurrentValue(navigation)!;

var jsonPropertyName = complexProperty.GetJsonPropertyName()!;
var complexPropertyValue = entry.GetCurrentValue(complexProperty);
writer.WritePropertyName(jsonPropertyName);

WriteJson(
writer,
ownedNavigationValue,
complexPropertyValue,
entry,
navigation.TargetEntityType,
complexProperty,
ordinal: null,
isCollection: navigation.IsCollection,
isCollection: complexProperty.IsCollection,
isTopLevel: false);
}

writer.WriteEndObject();
if (structuralType is IEntityType entityType)
{
foreach (var navigation in entityType.GetNavigations())
{
// skip back-references to the parent
if (navigation.IsOnDependent)
{
continue;
}

var jsonPropertyName = navigation.TargetEntityType.GetJsonPropertyName()!;
var ownedNavigationValue = entry.GetCurrentValue(navigation)!;

writer.WritePropertyName(jsonPropertyName);
WriteJson(
writer,
ownedNavigationValue,
entry,
navigation,
ordinal: null,
isCollection: navigation.IsCollection,
isTopLevel: false);
}
}
}

private ITableMapping? GetTableMapping(ITypeBase structuralType)
Expand Down
Loading