Skip to content

Commit 786751f

Browse files
authored
Merge pull request #31641 from dotnet-maestro-bot/merge/release/8.0-to-main
[automated] Merge branch 'release/8.0' => 'main'
2 parents 54ef202 + 9d217c7 commit 786751f

File tree

49 files changed

+1048
-315
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1048
-315
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Query.Internal;
5+
6+
/// <summary>
7+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11+
/// </summary>
12+
public readonly struct QueryableJsonProjectionInfo
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
public QueryableJsonProjectionInfo(
21+
Dictionary<IProperty, int> propertyIndexMap,
22+
List<(JsonProjectionInfo, INavigation)> childrenProjectionInfo)
23+
{
24+
PropertyIndexMap = propertyIndexMap;
25+
ChildrenProjectionInfo = childrenProjectionInfo;
26+
}
27+
28+
/// <summary>
29+
/// Map between entity properties and corresponding column indexes.
30+
/// </summary>
31+
/// <remarks>
32+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
33+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
34+
/// any release. You should only use it directly in your code with extreme caution and knowing that
35+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
36+
/// </remarks>
37+
public IDictionary<IProperty, int> PropertyIndexMap { get; }
38+
39+
/// <summary>
40+
/// Information needed to construct each child JSON entity.
41+
/// - JsonProjection info (same one we use for simple JSON projection),
42+
/// - navigation between parent and the child JSON entity.
43+
/// </summary>
44+
/// <remarks>
45+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
46+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
47+
/// any release. You should only use it directly in your code with extreme caution and knowing that
48+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
49+
/// </remarks>
50+
public IList<(JsonProjectionInfo JsonProjectionInfo, INavigation Navigation)> ChildrenProjectionInfo { get; }
51+
}

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,11 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression)
433433

434434
if (newExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression)
435435
{
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+
437441
_materializationContextBindings[parameterExpression] = propertyMap;
438442
_entityTypeIdentifyingExpressionInfo[parameterExpression] =
439443
// 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)
535539
visitedShaperResultParameter,
536540
shaper.Type);
537541
}
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+
}
538586
else
539587
{
540588
var entityParameter = Parameter(shaper.Type);
@@ -2141,6 +2189,62 @@ ParameterExpression ExtractAndCacheNonConstantJsonArrayElementAccessValue(int in
21412189
}
21422190
}
21432191

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+
21442248
private static LambdaExpression GenerateFixup(
21452249
Type entityType,
21462250
Type relatedEntityType,

0 commit comments

Comments
 (0)