@@ -1672,7 +1672,7 @@ private Expression CreateJsonShapers(
1672
1672
var elementFixup = Lambda (
1673
1673
Block (
1674
1674
typeof ( void ) ,
1675
- AssignReferenceRelationship (
1675
+ AssignStructuralProperty (
1676
1676
innerFixupCollectionElementParameter ,
1677
1677
innerFixupParentParameter ,
1678
1678
inverseNavigation ) ) ,
@@ -1709,7 +1709,7 @@ private Expression CreateJsonShapers(
1709
1709
{
1710
1710
var fixup = GenerateReferenceFixupForJson (
1711
1711
structuralType . ClrType ,
1712
- relatedStructuralType . ClrType ,
1712
+ nestedRelationship . ClrType ,
1713
1713
nestedRelationship ,
1714
1714
inverseNavigation ) ;
1715
1715
@@ -1847,8 +1847,27 @@ private Expression CreateJsonShapers(
1847
1847
return materializeJsonEntityCollectionMethodCall ;
1848
1848
}
1849
1849
1850
+
1851
+ // Return the materializer for this JSON object, including null checks which would return null.
1852
+ MethodInfo method ;
1853
+
1854
+ if ( relationship is not null && Nullable . GetUnderlyingType ( relationship . ClrType ) is { } underlyingType )
1855
+ {
1856
+ // The association property into which we're assigning has a nullable value type, so generate
1857
+ // a materializer that returns that nullable value type (note that the shaperLambda that
1858
+ // we pass itself always returns a non-nullable value (the null checks are outside of it.))
1859
+ Check . DebugAssert ( nullable , "On non-nullable relationship but the relationship's ClrType is Nullable<T>" ) ;
1860
+ Check . DebugAssert ( underlyingType == structuralType . ClrType ) ;
1861
+
1862
+ method = MaterializeJsonNullableValueStructuralTypeMethodInfo . MakeGenericMethod ( structuralType . ClrType ) ;
1863
+ }
1864
+ else
1865
+ {
1866
+ method = MaterializeJsonStructuralTypeMethodInfo . MakeGenericMethod ( structuralType . ClrType ) ;
1867
+ }
1868
+
1850
1869
var materializedRootJsonEntity = Call (
1851
- MaterializeJsonEntityMethodInfo . MakeGenericMethod ( structuralType . ClrType ) ,
1870
+ method ,
1852
1871
QueryCompilationContext . QueryContextParameter ,
1853
1872
keyValuesParameter ,
1854
1873
jsonReaderDataParameter ,
@@ -1969,9 +1988,9 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression)
1969
1988
1970
1989
var managerVariable = Variable ( typeof ( Utf8JsonReaderManager ) , "jsonReaderManager" ) ;
1971
1990
var tokenTypeVariable = Variable ( typeof ( JsonTokenType ) , "tokenType" ) ;
1972
- var jsonEntityTypeVariable = ( ParameterExpression ) jsonEntityTypeInitializerBlock . Expressions [ ^ 1 ] ;
1991
+ var jsonStructuralTypeVariable = ( ParameterExpression ) jsonEntityTypeInitializerBlock . Expressions [ ^ 1 ] ;
1973
1992
1974
- Debug . Assert ( jsonEntityTypeVariable . Type == structuralType . ClrType ) ;
1993
+ Debug . Assert ( jsonStructuralTypeVariable . Type == structuralType . ClrType ) ;
1975
1994
1976
1995
var finalBlockVariables = new List < ParameterExpression >
1977
1996
{
@@ -2024,7 +2043,7 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression)
2024
2043
// - navigation fixups
2025
2044
// - entity instance variable that is returned as end result
2026
2045
var propertyAssignmentReplacer = new ValueBufferTryReadValueMethodsReplacer (
2027
- jsonEntityTypeVariable , propertyAssignmentMap ) ;
2046
+ jsonStructuralTypeVariable , propertyAssignmentMap ) ;
2028
2047
2029
2048
if ( body . Expressions [ 0 ] is BinaryExpression
2030
2049
{
@@ -2051,7 +2070,7 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression)
2051
2070
// or for empty/null collections of a tracking queries.
2052
2071
ProcessFixup ( queryStateManager ? trackingInnerFixupMap : innerFixupMap ) ;
2053
2072
2054
- finalBlockExpressions . Add ( jsonEntityTypeVariable ) ;
2073
+ finalBlockExpressions . Add ( jsonStructuralTypeVariable ) ;
2055
2074
2056
2075
return Block (
2057
2076
finalBlockVariables ,
@@ -2063,18 +2082,35 @@ void ProcessFixup(IDictionary<string, LambdaExpression> fixupMap)
2063
2082
{
2064
2083
var navigationEntityParameter = _navigationVariableMap [ fixup . Key ] ;
2065
2084
2066
- // we need to add null checks before we run fixup logic. For regular entities, whose fixup is done as part of the "Materialize*" method
2067
- // the checks are done there (same will be done for the "optimized" scenario, where we populate properties directly rather than store in variables)
2068
- // but in this case fixups are standalone, so the null safety must be added by us directly
2069
- finalBlockExpressions . Add (
2070
- IfThen (
2071
- NotEqual (
2072
- jsonEntityTypeVariable ,
2073
- Constant ( null , jsonEntityTypeVariable . Type ) ) ,
2074
- Invoke (
2075
- fixup . Value ,
2076
- jsonEntityTypeVariable ,
2077
- _navigationVariableMap [ fixup . Key ] ) ) ) ;
2085
+ // Inject the fixup code for each property; we have this as a set of lambdas in the fixup map.
2086
+ // In the normal case, simply Invoke the lambda, passing it the structural type to be fixed up as a parameter.
2087
+ // This unfortunately doesn't work on value types (where a copy would be mutated), so for them,
2088
+ // we unwrap the lambda and integrate its body directly.
2089
+ // We should ideally do this for all cases (no need for the extra lambda Invoke), but there are some issues around us writing
2090
+ // to readonly fields.
2091
+ if ( jsonStructuralTypeVariable . Type . IsValueType /*&& Nullable.GetUnderlyingType(jsonStructuralTypeVariable.Type) is null*/ )
2092
+ {
2093
+ var fixupBody = ReplacingExpressionVisitor . Replace (
2094
+ originals : [ fixup . Value . Parameters [ 0 ] , fixup . Value . Parameters [ 1 ] ] ,
2095
+ replacements : [ jsonStructuralTypeVariable , _navigationVariableMap [ fixup . Key ] ] ,
2096
+ fixup . Value . Body ) ;
2097
+
2098
+ finalBlockExpressions . Add ( fixupBody ) ;
2099
+ }
2100
+ else
2101
+ {
2102
+ // If the structural type being fixed up is nullable, then we need to add null checks before we run fixup logic.
2103
+ // For regular entities, whose fixup is done as part of the "Materialize*" method, the checks are done there
2104
+ // (the same will be done for the "optimized" scenario, where we populate properties directly rather than store in variables).
2105
+ // But in this case fixups are standalone, so the null safety must be added here.
2106
+ finalBlockExpressions . Add (
2107
+ IfThen (
2108
+ NotEqual ( jsonStructuralTypeVariable , Constant ( null , jsonStructuralTypeVariable . Type ) ) ,
2109
+ Invoke (
2110
+ fixup . Value ,
2111
+ jsonStructuralTypeVariable ,
2112
+ _navigationVariableMap [ fixup . Key ] ) ) ) ;
2113
+ }
2078
2114
}
2079
2115
}
2080
2116
}
@@ -2756,7 +2792,7 @@ private LambdaExpression GenerateFixup(
2756
2792
expressions . Add (
2757
2793
relationship . IsCollection
2758
2794
? AddToCollectionRelationship ( entityParameter , relatedEntityParameter , relationship )
2759
- : AssignReferenceRelationship ( entityParameter , relatedEntityParameter , relationship ) ) ;
2795
+ : AssignStructuralProperty ( entityParameter , relatedEntityParameter , relationship ) ) ;
2760
2796
}
2761
2797
2762
2798
if ( inverseNavigation != null
@@ -2765,26 +2801,26 @@ private LambdaExpression GenerateFixup(
2765
2801
expressions . Add (
2766
2802
inverseNavigation . IsCollection
2767
2803
? AddToCollectionRelationship ( relatedEntityParameter , entityParameter , inverseNavigation )
2768
- : AssignReferenceRelationship ( relatedEntityParameter , entityParameter , inverseNavigation ) ) ;
2804
+ : AssignStructuralProperty ( relatedEntityParameter , entityParameter , inverseNavigation ) ) ;
2769
2805
}
2770
2806
2771
2807
return Lambda ( Block ( typeof ( void ) , expressions ) , entityParameter , relatedEntityParameter ) ;
2772
2808
}
2773
2809
2774
2810
private static LambdaExpression GenerateReferenceFixupForJson (
2775
- Type entityType ,
2776
- Type relatedEntityType ,
2811
+ Type clrType ,
2812
+ Type relatedClrType ,
2777
2813
IPropertyBase relationship ,
2778
2814
INavigationBase ? inverseNavigation )
2779
2815
{
2780
- var entityParameter = Parameter ( entityType ) ;
2781
- var relatedEntityParameter = Parameter ( relatedEntityType ) ;
2816
+ var entityParameter = Parameter ( clrType ) ;
2817
+ var relatedEntityParameter = Parameter ( relatedClrType ) ;
2782
2818
var expressions = new List < Expression > ( ) ;
2783
2819
2784
2820
if ( ! relationship . IsShadowProperty ( ) )
2785
2821
{
2786
2822
expressions . Add (
2787
- AssignReferenceRelationship (
2823
+ AssignStructuralProperty (
2788
2824
entityParameter ,
2789
2825
relatedEntityParameter ,
2790
2826
relationship ) ) ;
@@ -2794,7 +2830,7 @@ private static LambdaExpression GenerateReferenceFixupForJson(
2794
2830
&& ! inverseNavigation . IsShadowProperty ( ) )
2795
2831
{
2796
2832
expressions . Add (
2797
- AssignReferenceRelationship (
2833
+ AssignStructuralProperty (
2798
2834
relatedEntityParameter ,
2799
2835
entityParameter ,
2800
2836
inverseNavigation ) ) ;
@@ -2821,11 +2857,21 @@ public static void InverseCollectionFixup<TCollectionElement, TEntity>(
2821
2857
}
2822
2858
}
2823
2859
2824
- private static Expression AssignReferenceRelationship (
2825
- ParameterExpression entity ,
2826
- ParameterExpression relatedEntity ,
2827
- IPropertyBase relationship )
2828
- => entity . MakeMemberAccess ( relationship . GetMemberInfo ( forMaterialization : true , forSet : true ) ) . Assign ( relatedEntity ) ;
2860
+ private static Expression AssignStructuralProperty (
2861
+ ParameterExpression structuralType ,
2862
+ ParameterExpression relatedStructuralType ,
2863
+ IPropertyBase structuralProperty )
2864
+ {
2865
+ var setter = structuralProperty . GetMemberInfo ( forMaterialization : true , forSet : true ) ;
2866
+
2867
+ // If we're assigning a value complex type to a nullable complex property, add an upcast for typing
2868
+ var assignee = structuralProperty . ClrType . IsNullableValueType ( )
2869
+ && structuralProperty . ClrType . UnwrapNullableType ( ) == relatedStructuralType . Type
2870
+ ? Convert ( relatedStructuralType , structuralProperty . ClrType )
2871
+ : ( Expression ) relatedStructuralType ;
2872
+
2873
+ return structuralType . MakeMemberAccess ( setter ) . Assign ( assignee ) ;
2874
+ }
2829
2875
2830
2876
private Expression GetOrCreateCollectionObjectLambda ( Type entityType , IPropertyBase relationship )
2831
2877
{
0 commit comments