Skip to content

Commit 60956cf

Browse files
CopilotAndriySvyryd
andcommitted
Fix InvalidCastException in ArrayPropertyValues.ToObject() with nested nullable complex properties
Fixes #37516 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
1 parent 9dae88e commit 60956cf

2 files changed

Lines changed: 437 additions & 36 deletions

File tree

src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public class ArrayPropertyValues : PropertyValues
1818
private readonly List<ArrayPropertyValues?>?[] _complexCollectionValues;
1919
private readonly bool[]? _nullComplexPropertyFlags;
2020

21+
private static readonly bool UseOldBehavior37516 =
22+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37516", out var enabled) && enabled;
23+
2124
/// <summary>
2225
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2326
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -50,7 +53,7 @@ public override object ToObject()
5053
if (_nullComplexPropertyFlags[i])
5154
{
5255
var complexProperty = NullableComplexProperties[i];
53-
structuralObject = ((IRuntimeComplexProperty)complexProperty).GetSetter().SetClrValue(structuralObject, null);
56+
structuralObject = SetNestedComplexPropertyValue(structuralObject, complexProperty, null);
5457
}
5558
}
5659
}
@@ -68,7 +71,7 @@ public override object ToObject()
6871
!complexProperty.IsShadowProperty(),
6972
$"Shadow complex property {complexProperty.Name} is not supported. Issue #31243");
7073
var list = (IList)((IRuntimeComplexProperty)complexProperty).GetIndexedCollectionAccessor().Create(propertyValuesList.Count);
71-
structuralObject = ((IRuntimeComplexProperty)complexProperty).GetSetter().SetClrValue(structuralObject, list);
74+
structuralObject = SetNestedComplexPropertyValue(structuralObject, complexProperty, list);
7275

7376
foreach (var propertyValues in propertyValuesList)
7477
{
@@ -79,6 +82,33 @@ public override object ToObject()
7982
return structuralObject;
8083
}
8184

85+
private object SetNestedComplexPropertyValue(object structuralObject, IComplexProperty complexProperty, object? value)
86+
{
87+
return UseOldBehavior37516
88+
? ((IRuntimeComplexProperty)complexProperty).GetSetter().SetClrValue(structuralObject, value)
89+
: SetValueRecursively(structuralObject, complexProperty.GetChainToComplexProperty(fromEntity: false), 0, value);
90+
91+
static object SetValueRecursively(object instance, IReadOnlyList<IComplexProperty> chain, int index, object? value)
92+
{
93+
var currentProperty = (IRuntimeComplexProperty)chain[index];
94+
if (index == chain.Count - 1)
95+
{
96+
return currentProperty.GetSetter().SetClrValue(instance, value);
97+
}
98+
99+
var child = currentProperty.GetGetter().GetClrValue(instance);
100+
if (child == null)
101+
{
102+
return instance;
103+
}
104+
105+
var updated = SetValueRecursively(child, chain, index + 1, value);
106+
// Need to update the child value as well because it could be a value type
107+
// TODO: Improve this, see #36041
108+
return currentProperty.GetSetter().SetClrValue(instance, updated);
109+
}
110+
}
111+
82112
/// <summary>
83113
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
84114
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -495,20 +525,44 @@ ArrayPropertyValues CreateComplexPropertyValues(object complexObject, InternalCo
495525
for (var i = 0; i < properties.Count; i++)
496526
{
497527
var property = properties[i];
498-
var getter = property.GetGetter();
499-
values[i] = getter.GetClrValue(complexObject);
528+
var targetObject = NavigateToDeclaringType(complexObject, property.DeclaringType, complexType);
529+
values[i] = targetObject == null ? null : property.GetGetter().GetClrValue(targetObject);
500530
}
501531

502532
var complexPropertyValues = new ArrayPropertyValues(entry, values, null);
503533

504534
foreach (var nestedComplexProperty in complexPropertyValues.ComplexCollectionProperties)
505535
{
506-
var nestedCollection = (IList?)nestedComplexProperty.GetGetter().GetClrValue(complexObject);
507-
var propertyValuesList = GetComplexCollectionPropertyValues(nestedComplexProperty, nestedCollection);
508-
complexPropertyValues.SetComplexCollectionValue(nestedComplexProperty, propertyValuesList);
536+
var targetObject = NavigateToDeclaringType(complexObject, nestedComplexProperty.DeclaringType, complexType);
537+
var nestedCollection = targetObject == null ? null : (IList?)nestedComplexProperty.GetGetter().GetClrValue(targetObject);
538+
var nestedPropertyValuesList = GetComplexCollectionPropertyValues(nestedComplexProperty, nestedCollection);
539+
complexPropertyValues.SetComplexCollectionValue(nestedComplexProperty, nestedPropertyValuesList);
509540
}
510541

511542
return complexPropertyValues;
512543
}
544+
545+
static object? NavigateToDeclaringType(object root, ITypeBase declaringType, IRuntimeTypeBase rootType)
546+
{
547+
if (declaringType == rootType
548+
|| UseOldBehavior37516)
549+
{
550+
return root;
551+
}
552+
553+
if (declaringType is not IComplexType ct)
554+
{
555+
return root;
556+
}
557+
558+
var chain = ct.ComplexProperty.GetChainToComplexProperty(fromEntity: false);
559+
object? target = root;
560+
for (var i = 0; i < chain.Count && target != null; i++)
561+
{
562+
target = chain[i].GetGetter().GetClrValue(target);
563+
}
564+
565+
return target;
566+
}
513567
}
514568
}

0 commit comments

Comments
 (0)