@@ -433,7 +433,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
433
433
434
434
if ( newExpression . Arguments [ 0 ] is ProjectionBindingExpression projectionBindingExpression )
435
435
{
436
- var propertyMap = ( IDictionary < IProperty , int > ) GetProjectionIndex ( projectionBindingExpression ) ;
436
+ var projectionIndex = GetProjectionIndex ( projectionBindingExpression ) ;
437
+ var propertyMap = projectionIndex is IDictionary < IProperty , int >
438
+ ? ( IDictionary < IProperty , int > ) projectionIndex
439
+ : ( ( QueryableJsonProjectionInfo ) projectionIndex ) . PropertyIndexMap ;
440
+
437
441
_materializationContextBindings [ parameterExpression ] = propertyMap ;
438
442
_entityTypeIdentifyingExpressionInfo [ parameterExpression ] =
439
443
// If single entity type is being selected in hierarchy then we use the value directly else we store the offset
@@ -535,6 +539,50 @@ protected override Expression VisitExtension(Expression extensionExpression)
535
539
visitedShaperResultParameter ,
536
540
shaper . Type ) ;
537
541
}
542
+ else if ( GetProjectionIndex ( projectionBindingExpression ) is QueryableJsonProjectionInfo queryableJsonEntityProjectionInfo )
543
+ {
544
+ if ( _isTracking )
545
+ {
546
+ throw new InvalidOperationException (
547
+ RelationalStrings . JsonEntityOrCollectionProjectedAtRootLevelInTrackingQuery ( nameof ( EntityFrameworkQueryableExtensions . AsNoTracking ) ) ) ;
548
+ }
549
+
550
+ // json entity converted to query root and projected
551
+ var entityParameter = Parameter ( shaper . Type ) ;
552
+ _variables . Add ( entityParameter ) ;
553
+ var entityMaterializationExpression = ( BlockExpression ) _parentVisitor . InjectEntityMaterializers ( shaper ) ;
554
+
555
+ var mappedProperties = queryableJsonEntityProjectionInfo . PropertyIndexMap . Keys . ToList ( ) ;
556
+ var rewrittenEntityMaterializationExpression = new QueryableJsonEntityMaterializerRewriter ( mappedProperties )
557
+ . Rewrite ( entityMaterializationExpression ) ;
558
+
559
+ var visitedEntityMaterializationExpression = Visit ( rewrittenEntityMaterializationExpression ) ;
560
+ _expressions . Add ( Assign ( entityParameter , visitedEntityMaterializationExpression ) ) ;
561
+
562
+ foreach ( var childProjectionInfo in queryableJsonEntityProjectionInfo . ChildrenProjectionInfo )
563
+ {
564
+ var ( jsonReaderDataVariable , keyValuesParameter ) = JsonShapingPreProcess (
565
+ childProjectionInfo . JsonProjectionInfo ,
566
+ childProjectionInfo . Navigation . TargetEntityType ,
567
+ childProjectionInfo . Navigation . IsCollection ) ;
568
+
569
+ var shaperResult = CreateJsonShapers (
570
+ childProjectionInfo . Navigation . TargetEntityType ,
571
+ nullable : true ,
572
+ jsonReaderDataVariable ,
573
+ keyValuesParameter ,
574
+ parentEntityExpression : entityParameter ,
575
+ navigation : childProjectionInfo . Navigation ) ;
576
+
577
+ var visitedShaperResult = Visit ( shaperResult ) ;
578
+
579
+ _includeExpressions . Add ( visitedShaperResult ) ;
580
+ }
581
+
582
+ accessor = CompensateForCollectionMaterialization (
583
+ entityParameter ,
584
+ shaper . Type ) ;
585
+ }
538
586
else
539
587
{
540
588
var entityParameter = Parameter ( shaper . Type ) ;
@@ -2141,6 +2189,62 @@ ParameterExpression ExtractAndCacheNonConstantJsonArrayElementAccessValue(int in
2141
2189
}
2142
2190
}
2143
2191
2192
+ private sealed class QueryableJsonEntityMaterializerRewriter : ExpressionVisitor
2193
+ {
2194
+ private readonly List < IProperty > _mappedProperties ;
2195
+
2196
+ public QueryableJsonEntityMaterializerRewriter ( List < IProperty > mappedProperties )
2197
+ {
2198
+ _mappedProperties = mappedProperties ;
2199
+ }
2200
+
2201
+ public BlockExpression Rewrite ( BlockExpression jsonEntityShaperMaterializer )
2202
+ => ( BlockExpression ) VisitBlock ( jsonEntityShaperMaterializer ) ;
2203
+
2204
+ protected override Expression VisitBinary ( BinaryExpression binaryExpression )
2205
+ {
2206
+ // here we try to pattern match part of the shaper code that checks if key values are null
2207
+ // if they are all non-null then we generate the entity
2208
+ // problem for JSON entities is that some of the keys are synthesized and should be omitted
2209
+ // if the key is one of the mapped ones, we leave the expression as is, otherwise replace with Constant(true)
2210
+ // i.e. removing it
2211
+ if ( binaryExpression is
2212
+ {
2213
+ NodeType : ExpressionType . NotEqual ,
2214
+ Left : MethodCallExpression
2215
+ {
2216
+ Method : { IsGenericMethod : true } method ,
2217
+ Arguments : [ _, _, ConstantExpression { Value : IProperty property } ]
2218
+ } ,
2219
+ Right : ConstantExpression { Value : null }
2220
+ }
2221
+ && method . GetGenericMethodDefinition ( ) == Infrastructure . ExpressionExtensions . ValueBufferTryReadValueMethod )
2222
+ {
2223
+ return _mappedProperties . Contains ( property )
2224
+ ? binaryExpression
2225
+ : Constant ( true ) ;
2226
+ }
2227
+
2228
+ return base . VisitBinary ( binaryExpression ) ;
2229
+ }
2230
+
2231
+ protected override Expression VisitMethodCall ( MethodCallExpression methodCallExpression )
2232
+ {
2233
+ if ( methodCallExpression is
2234
+ {
2235
+ Method : { IsGenericMethod : true } method ,
2236
+ Arguments : [ _, _, ConstantExpression { Value : IProperty property } ]
2237
+ }
2238
+ && method . GetGenericMethodDefinition ( ) == Infrastructure . ExpressionExtensions . ValueBufferTryReadValueMethod
2239
+ && ! _mappedProperties . Contains ( property ) )
2240
+ {
2241
+ return Default ( methodCallExpression . Type ) ;
2242
+ }
2243
+
2244
+ return base . VisitMethodCall ( methodCallExpression ) ;
2245
+ }
2246
+ }
2247
+
2144
2248
private static LambdaExpression GenerateFixup (
2145
2249
Type entityType ,
2146
2250
Type relatedEntityType ,
0 commit comments