Skip to content

Commit

Permalink
Query: Introduce client projections in SelectExpression (#24675)
Browse files Browse the repository at this point in the history
- ClientProjections is alternate when ProjectionMember binding is not possible using indexes
- ClientProjections is separate from SelectExpression.Projection where former holds complex client side mapped projections and later holds only scalars
- What it enables
  - Single result subquery is not joined right away
  - pendingCollections are removed
  - Ability to bind with above subquery translations after projection has converted to index based binding (enabling pending selector future change)
  - Ability to bind client projections smoothly like ProjectionMember binding so suppose translations post-client eval where it is supported
  - Grouping.FirstOrDefault can add subquery projection which applies separately so we can combine aggregate/non-aggregate projection on grouping
- Introduce CollectionResultExpression which holds info on how to create collection for a subquery
- ApplyProjection converts projectionMember/Index based bindings to actual scalar projection and updates shaper in same pass
- Unique collectionId are assigned by shaper
- Change in flow to projection to let sqlTranslator determines translatibility before processing for client side
- Regression in collection over single result subquery
  - Earlier we applied single result right away copying over pending collection to outer which allowed single result subquery to convert apply to join
  - Now all client projections apply at same time so pending collection (equivalent subquery) is applied inside single result subquery so we fail to convert apply to join

Ground work for #20291, #12088, #13805
Resolves #23571
  • Loading branch information
smitpatel authored Apr 20, 2021
1 parent 277b43a commit 7e2c5ce
Show file tree
Hide file tree
Showing 37 changed files with 1,903 additions and 1,568 deletions.
99 changes: 99 additions & 0 deletions src/EFCore.Relational/Query/CollectionResultExpression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Query
{
/// <summary>
/// <para>
/// An expression that represents creation of a collection in <see cref="ShapedQueryExpression.ShaperExpression" /> for relational providers.
/// </para>
/// <para>
/// This type is typically used by database providers (and other extensions). It is generally
/// not used in application code.
/// </para>
/// </summary>
public class CollectionResultExpression : Expression, IPrintableExpression
{
/// <summary>
/// Creates a new instance of the <see cref="CollectionResultExpression" /> class.
/// </summary>
/// <param name="projectionBindingExpression"> An expression reprensenting how to get the subquery from SelectExpression to get the elements. </param>
/// <param name="navigation"> A navigation associated with this collection, if any. </param>
/// <param name="elementType"> The clr type of individual elements in the collection. </param>
public CollectionResultExpression(
ProjectionBindingExpression projectionBindingExpression,
INavigationBase? navigation,
Type elementType)
{
ProjectionBindingExpression = projectionBindingExpression;
Navigation = navigation;
ElementType = elementType;
}

/// <summary>
/// The expression to get the subquery for this collection.
/// </summary>
public virtual ProjectionBindingExpression ProjectionBindingExpression { get; }
/// <summary>
/// The navigation if associated with the collection.
/// </summary>
public virtual INavigationBase? Navigation { get; }
/// <summary>
/// The clr type of elements of the collection.
/// </summary>
public virtual Type ElementType { get; }

/// <inheritdoc />
public override Type Type => ProjectionBindingExpression.Type;
/// <inheritdoc />
public override ExpressionType NodeType => ExpressionType.Extension;

/// <inheritdoc />
protected override Expression VisitChildren(ExpressionVisitor visitor)
{
Check.NotNull(visitor, nameof(visitor));

var projectionBindingExpression = (ProjectionBindingExpression)visitor.Visit(ProjectionBindingExpression);

return Update(projectionBindingExpression);
}

/// <summary>
/// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="projectionBindingExpression"> The <see cref="ProjectionBindingExpression" /> property of the result. </param>
/// <returns> This expression if no children changed, or an expression with the updated children. </returns>
public virtual CollectionResultExpression Update(ProjectionBindingExpression projectionBindingExpression)
{
Check.NotNull(projectionBindingExpression, nameof(projectionBindingExpression));

return projectionBindingExpression != ProjectionBindingExpression
? new CollectionResultExpression(projectionBindingExpression, Navigation, ElementType)
: this;
}

/// <inheritdoc />
public virtual void Print(ExpressionPrinter expressionPrinter)
{
expressionPrinter.AppendLine("CollectionResultExpression:");
using (expressionPrinter.Indent())
{
expressionPrinter.Append("ProjectionBindingExpression:");
expressionPrinter.Visit(ProjectionBindingExpression);
expressionPrinter.AppendLine();
if (Navigation != null)
{
expressionPrinter.Append("Navigation:").AppendLine(Navigation.ToString()!);
}
expressionPrinter.Append("ElementType:").AppendLine(ElementType.ShortDisplayName());
}
}
}
}
3 changes: 3 additions & 0 deletions src/EFCore.Relational/Query/EntityProjectionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,8 @@ public virtual void AddNavigationBinding(INavigation navigation, EntityShaperExp
? expression
: null;
}

/// <inheritdoc />
public override string ToString() => $"EntityProjectionExpression: {EntityType.ShortName()}";
}
}

This file was deleted.

Loading

0 comments on commit 7e2c5ce

Please sign in to comment.