11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Reflection . Metadata . Ecma335 ;
45using Microsoft . EntityFrameworkCore . Diagnostics . Internal ;
56using Microsoft . EntityFrameworkCore . Internal ;
7+ using Microsoft . EntityFrameworkCore . Metadata ;
8+ using Microsoft . EntityFrameworkCore . Metadata . Internal ;
69using static System . Linq . Expressions . Expression ;
710
811namespace Microsoft . EntityFrameworkCore . Query . Internal ;
@@ -95,28 +98,46 @@ public Expression CreateMaterializeExpression(
9598 properties . Remove ( consumedProperty ) ;
9699 }
97100
98- var constructorExpression = constructorBinding . CreateConstructorExpression ( bindingInfo ) ;
101+ var materializationExpression = HandleMaterializationInterception ( ) ;
99102
100- if ( _materializationInterceptor == null
101- // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types
102- // don't get intercepted. #35883
103- || structuralType is not IEntityType )
103+ return
104+ structuralType is IComplexType complexType && ReadComplexTypeDirectly ( complexType )
105+ && ( IsNullable ( complexType ) || parameters . AllowNullable == true )
106+ ? HandleNullableComplexTypeMaterialization (
107+ complexType ,
108+ complexType . ClrType ,
109+ materializationExpression ,
110+ bindingInfo )
111+ : materializationExpression ;
112+
113+ Expression HandleMaterializationInterception ( )
104114 {
105- return properties . Count == 0 && blockExpressions . Count == 0
106- ? constructorExpression
107- : CreateMaterializeExpression ( blockExpressions , instanceVariable , constructorExpression , properties , bindingInfo ) ;
115+ var constructorExpression = constructorBinding . CreateConstructorExpression ( bindingInfo ) ;
116+
117+ return _materializationInterceptor == null
118+ // TODO: This currently applies the materialization interceptor only on the root structural type - any contained complex types
119+ // don't get intercepted. #35883
120+ || structuralType is not IEntityType
121+ ? properties . Count == 0 && blockExpressions . Count == 0
122+ ? constructorExpression
123+ : CreateMaterializeExpression ( blockExpressions , instanceVariable , constructorExpression , properties , bindingInfo )
124+ : CreateInterceptionMaterializeExpression (
125+ structuralType ,
126+ properties ,
127+ _materializationInterceptor ,
128+ bindingInfo ,
129+ constructorExpression ,
130+ instanceVariable ,
131+ blockExpressions ) ;
108132 }
109-
110- return CreateInterceptionMaterializeExpression (
111- structuralType ,
112- properties ,
113- _materializationInterceptor ,
114- bindingInfo ,
115- constructorExpression ,
116- instanceVariable ,
117- blockExpressions ) ;
118133 }
119134
135+ /// <summary>
136+ /// Should complex type be read directly using e.g. DbDataReader.GetFieldValue
137+ /// or is it going to be handled separately (i.e. relational JSON).
138+ /// </summary>
139+ protected virtual bool ReadComplexTypeDirectly ( IComplexType complexType ) => true ;
140+
120141 /// <summary>
121142 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
122143 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -130,11 +151,17 @@ protected virtual void AddInitializeExpression(
130151 MethodCallExpression valueBufferExpression ,
131152 List < Expression > blockExpressions )
132153 {
154+ if ( property is IComplexProperty cp && ! ReadComplexTypeDirectly ( cp . ComplexType ) )
155+ {
156+ return ;
157+ }
158+
133159 var memberInfo = property . GetMemberInfo ( forMaterialization : true , forSet : true ) ;
134160
135161 var valueExpression = property switch
136162 {
137- IProperty p => valueBufferExpression . CreateValueBufferReadValueExpression ( memberInfo . GetMemberType ( ) , p . GetIndex ( ) , p ) ,
163+ IProperty p
164+ => valueBufferExpression . CreateValueBufferReadValueExpression ( memberInfo . GetMemberType ( ) , p . GetIndex ( ) , p ) ,
138165
139166 IServiceProperty serviceProperty
140167 => serviceProperty . ParameterBinding . BindToParameter ( bindingInfo ) ,
@@ -143,9 +170,7 @@ IServiceProperty serviceProperty
143170 => Default ( complexProperty . ClrType ) , // Initialize collections to null, they'll be populated separately
144171
145172 IComplexProperty complexProperty
146- => CreateMaterializeExpression (
147- new StructuralTypeMaterializerSourceParameters ( complexProperty . ComplexType , "complexType" , QueryTrackingBehavior : null ) ,
148- bindingInfo . MaterializationContextExpression ) ,
173+ => CreateComplexTypeMaterializeExpression ( complexProperty , bindingInfo ) ,
149174
150175 _ => throw new UnreachableException ( )
151176 } ;
@@ -193,6 +218,21 @@ static Expression CreateMemberAssignment(Expression parameter, MemberInfo member
193218 value )
194219 : MakeMemberAccess ( parameter , memberInfo ) . Assign ( value ) ;
195220 }
221+
222+ Expression CreateComplexTypeMaterializeExpression ( IComplexProperty complexProperty , ParameterBindingInfo bindingInfo )
223+ {
224+ var materializeExpression = CreateMaterializeExpression (
225+ new StructuralTypeMaterializerSourceParameters ( complexProperty . ComplexType , "complexType" , null , QueryTrackingBehavior : null ) ,
226+ bindingInfo . MaterializationContextExpression ) ;
227+
228+ return IsNullable ( complexProperty )
229+ ? HandleNullableComplexTypeMaterialization (
230+ complexProperty . ComplexType ,
231+ complexProperty . ClrType ,
232+ materializeExpression ,
233+ bindingInfo )
234+ : materializeExpression ;
235+ }
196236 }
197237
198238 private void AddInitializeExpressions (
@@ -493,15 +533,14 @@ BlockExpression CreateInitializeExpression()
493533 /// any release. You should only use it directly in your code with extreme caution and knowing that
494534 /// doing so can result in application failures when updating to a new Entity Framework Core release.
495535 /// </summary>
496- public virtual Func < MaterializationContext , object > GetMaterializer (
497- IEntityType entityType )
536+ public virtual Func < MaterializationContext , object > GetMaterializer ( IEntityType entityType )
498537 {
499538 var materializationContextParameter
500539 = Parameter ( typeof ( MaterializationContext ) , "materializationContext" ) ;
501540
502541 return Lambda < Func < MaterializationContext , object > > (
503542 ( ( IStructuralTypeMaterializerSource ) this ) . CreateMaterializeExpression (
504- new StructuralTypeMaterializerSourceParameters ( entityType , "instance" , null ) , materializationContextParameter ) ,
543+ new StructuralTypeMaterializerSourceParameters ( entityType , "instance" , null , null ) , materializationContextParameter ) ,
505544 materializationContextParameter )
506545 . Compile ( ) ;
507546 }
@@ -518,7 +557,7 @@ public virtual Func<MaterializationContext, object> GetMaterializer(IComplexType
518557
519558 return Lambda < Func < MaterializationContext , object > > (
520559 ( ( IStructuralTypeMaterializerSource ) this ) . CreateMaterializeExpression (
521- new StructuralTypeMaterializerSourceParameters ( complexType , "instance" , null ) , materializationContextParameter ) ,
560+ new StructuralTypeMaterializerSourceParameters ( complexType , "instance" , null , null ) , materializationContextParameter ) ,
522561 materializationContextParameter )
523562 . Compile ( ) ;
524563 }
@@ -572,7 +611,7 @@ public virtual Func<MaterializationContext, object> GetEmptyMaterializer(
572611
573612 var materializationContextExpression = Parameter ( typeof ( MaterializationContext ) , "mc" ) ;
574613 var bindingInfo = new ParameterBindingInfo (
575- new StructuralTypeMaterializerSourceParameters ( entityType , "instance" , null ) , materializationContextExpression ) ;
614+ new StructuralTypeMaterializerSourceParameters ( entityType , "instance" , null , null ) , materializationContextExpression ) ;
576615
577616 var blockExpressions = new List < Expression > ( ) ;
578617 var instanceVariable = Variable ( binding . RuntimeType , "instance" ) ;
@@ -644,4 +683,46 @@ private static void CreateServiceInstances(
644683 }
645684 }
646685 }
686+
687+ private Expression HandleNullableComplexTypeMaterialization ( IComplexType complexType , Type clrType , Expression materializeExpression , ParameterBindingInfo bindingInfo )
688+ {
689+ var valueBufferExpression = Call (
690+ bindingInfo . MaterializationContextExpression ,
691+ MaterializationContext . GetValueBufferMethod ) ;
692+
693+ // Get all scalar properties of the complex type (including nested ones).
694+ var allScalarProperties = complexType . GetFlattenedProperties ( )
695+ . Where ( p => ! p . IsShadowProperty ( ) ) . ToList ( ) ;
696+
697+ if ( allScalarProperties is [ ] )
698+ {
699+ // If no scalar properties, just create the instance.
700+ return CreateMaterializeExpression (
701+ new StructuralTypeMaterializerSourceParameters ( complexType , "complexType" , null , QueryTrackingBehavior : null ) ,
702+ bindingInfo . MaterializationContextExpression ) ;
703+ }
704+
705+ // Create null checks for all scalar properties using direct ValueBuffer access.
706+ var nullChecks = allScalarProperties . Select ( p =>
707+ Equal (
708+ valueBufferExpression . CreateValueBufferReadValueExpression ( typeof ( object ) , p . GetIndex ( ) , p ) ,
709+ Constant ( null , typeof ( object ) ) ) ) ;
710+
711+ // Combine all null checks with AndAlso.
712+ var allNullCondition = nullChecks . Aggregate ( AndAlso ) ;
713+
714+ // If all properties are null, return null; otherwise materialize the complex type.
715+ return Condition (
716+ allNullCondition ,
717+ Default ( clrType ) ,
718+ materializeExpression ) ;
719+ }
720+
721+ private static bool IsNullable ( IComplexType complexType )
722+ => IsNullable ( complexType . ComplexProperty ) ;
723+
724+ private static bool IsNullable ( IComplexProperty complexProperty )
725+ => complexProperty . IsNullable
726+ || ( complexProperty . DeclaringType is IComplexType complexType
727+ && IsNullable ( complexType . ComplexProperty ) ) ;
647728}
0 commit comments