/// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        protected override Expression VisitMember(MemberExpression memberExpression)
        {
            var newExpression = Visit(memberExpression.Expression);

            if (newExpression is SubQueryExpression subQueryExpression)
            {
                var subSelector = subQueryExpression.QueryModel.SelectClause.Selector;
                if ((subSelector is QuerySourceReferenceExpression || subSelector is SubQueryExpression) &&
                    !subQueryExpression.QueryModel.ResultOperators.Any(
                        ro =>
                        ro is ConcatResultOperator ||
                        ro is UnionResultOperator ||
                        ro is IntersectResultOperator ||
                        ro is ExceptResultOperator))
                {
                    if (!subQueryExpression.QueryModel.ResultOperators.Any(ro => ro is DistinctResultOperator))
                    {
                        var querySourceMapping = new QuerySourceMapping();
                        var subQueryModel      = subQueryExpression.QueryModel.Clone(querySourceMapping);
                        _queryCompilationContext.UpdateMapping(querySourceMapping);

                        subQueryModel.SelectClause.Selector = VisitMember(memberExpression.Update(subQueryModel.SelectClause.Selector));
                        subQueryModel.ResultTypeOverride    = subQueryModel.SelectClause.Selector.Type;

                        return(new SubQueryExpression(subQueryModel));
                    }

                    var finalResultOperator = subQueryExpression.QueryModel.ResultOperators.Last();
                    if (finalResultOperator is FirstResultOperator ||
                        finalResultOperator is SingleResultOperator ||
                        finalResultOperator is LastResultOperator)
                    {
                        var queryModel = subQueryExpression.QueryModel;
                        queryModel.ResultOperators.Remove(finalResultOperator);

                        queryModel.ResultTypeOverride = null;
                        var newSubQueryExpression = new SubQueryExpression(queryModel);

                        var mainFromClause = new MainFromClause(queryModel.GetNewName("subquery"), queryModel.SelectClause.Selector.Type, newSubQueryExpression);
                        var selector       = Expression.MakeMemberAccess(
                            new QuerySourceReferenceExpression(mainFromClause),
                            memberExpression.Member);

                        var subqueryModel = new QueryModel(mainFromClause, new SelectClause(selector));
                        subqueryModel.ResultOperators.Add(finalResultOperator);
                        var subqueryExpression = new SubQueryExpression(subqueryModel);

                        return(subqueryExpression);
                    }
                }
            }

            return(memberExpression.Update(newExpression));
        }
        /// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        protected override Expression VisitMember(MemberExpression memberExpression)
        {
            var newExpression = Visit(memberExpression.Expression);

            var subQueryExpression = newExpression as SubQueryExpression;
            var subSelector        = subQueryExpression?.QueryModel.SelectClause.Selector;

            if (subSelector is QuerySourceReferenceExpression ||
                subSelector is SubQueryExpression)
            {
                var querySourceMapping = new QuerySourceMapping();
                var subQueryModel      = subQueryExpression.QueryModel.Clone(querySourceMapping);
                _queryCompilationContext.UpdateMapping(querySourceMapping);

                subQueryModel.SelectClause.Selector = VisitMember(memberExpression.Update(subQueryModel.SelectClause.Selector));
                subQueryModel.ResultTypeOverride    = subQueryModel.SelectClause.Selector.Type;

                return(new SubQueryExpression(subQueryModel));
            }

            return(memberExpression.Update(newExpression));
        }
Exemple #3
0
            private void Rewrite(QueryModel collectionQueryModel, INavigation navigation)
            {
                var querySourceReferenceFindingExpressionTreeVisitor
                    = new QuerySourceReferenceFindingExpressionTreeVisitor();

                var whereClause = collectionQueryModel.BodyClauses
                                  .OfType <WhereClause>()
                                  .Single();

                whereClause.TransformExpressions(querySourceReferenceFindingExpressionTreeVisitor.Visit);

                collectionQueryModel.BodyClauses.Remove(whereClause);

                var parentQuerySourceReferenceExpression
                    = querySourceReferenceFindingExpressionTreeVisitor.QuerySourceReferenceExpression;

                var parentQuerySource = parentQuerySourceReferenceExpression.ReferencedQuerySource;

                BuildParentOrderings(
                    _parentQueryModel,
                    navigation,
                    parentQuerySourceReferenceExpression,
                    ParentOrderings);

                var querySourceMapping     = new QuerySourceMapping();
                var clonedParentQueryModel = _parentQueryModel.Clone(querySourceMapping);

                _queryCompilationContext.UpdateMapping(querySourceMapping);

                _queryCompilationContext.CloneAnnotations(querySourceMapping, clonedParentQueryModel);

                var clonedParentQuerySourceReferenceExpression
                    = (QuerySourceReferenceExpression)querySourceMapping.GetExpression(parentQuerySource);

                var clonedParentQuerySource
                    = clonedParentQuerySourceReferenceExpression.ReferencedQuerySource;

                AdjustPredicate(
                    clonedParentQueryModel,
                    clonedParentQuerySource,
                    clonedParentQuerySourceReferenceExpression);

                clonedParentQueryModel.SelectClause
                    = new SelectClause(Expression.Default(typeof(AnonymousObject)));

                var subQueryProjection = new List <Expression>();

                var lastResultOperator = ProcessResultOperators(clonedParentQueryModel);

                clonedParentQueryModel.ResultTypeOverride
                    = typeof(IQueryable <>).MakeGenericType(clonedParentQueryModel.SelectClause.Selector.Type);

                var parentItemName
                    = parentQuerySource.HasGeneratedItemName()
                        ? navigation.DeclaringEntityType.DisplayName()[0].ToString().ToLowerInvariant()
                        : parentQuerySource.ItemName;

                collectionQueryModel.MainFromClause.ItemName = $"{parentItemName}.{navigation.Name}";

                var collectionQuerySourceReferenceExpression
                    = new QuerySourceReferenceExpression(collectionQueryModel.MainFromClause);

                var joinQuerySourceReferenceExpression
                    = CreateJoinToParentQuery(
                          clonedParentQueryModel,
                          clonedParentQuerySourceReferenceExpression,
                          collectionQuerySourceReferenceExpression,
                          navigation.ForeignKey,
                          collectionQueryModel,
                          subQueryProjection);

                ApplyParentOrderings(
                    ParentOrderings,
                    clonedParentQueryModel,
                    querySourceMapping,
                    lastResultOperator);

                LiftOrderBy(
                    clonedParentQuerySource,
                    joinQuerySourceReferenceExpression,
                    clonedParentQueryModel,
                    collectionQueryModel,
                    subQueryProjection);

                clonedParentQueryModel.SelectClause.Selector
                    = Expression.New(
                          AnonymousObject.AnonymousObjectCtor,
                          Expression.NewArrayInit(
                              typeof(object),
                              subQueryProjection));
            }
Exemple #4
0
        private Expression Rewrite(
            int correlatedCollectionIndex,
            QueryModel collectionQueryModel,
            INavigation navigation,
            bool trackingQuery,
            QuerySourceReferenceExpression originQuerySource,
            bool forceListResult)
        {
            var querySourceReferenceFindingExpressionTreeVisitor
                = new QuerySourceReferenceFindingExpressionTreeVisitor();

            var originalCorrelationPredicate = collectionQueryModel.BodyClauses.OfType <WhereClause>().Single(c => c.Predicate is NullConditionalEqualExpression);

            collectionQueryModel.BodyClauses.Remove(originalCorrelationPredicate);

            originalCorrelationPredicate.TransformExpressions(querySourceReferenceFindingExpressionTreeVisitor.Visit);
            var parentQuerySourceReferenceExpression = querySourceReferenceFindingExpressionTreeVisitor.QuerySourceReferenceExpression;

            querySourceReferenceFindingExpressionTreeVisitor = new QuerySourceReferenceFindingExpressionTreeVisitor();
            querySourceReferenceFindingExpressionTreeVisitor.Visit(((NullConditionalEqualExpression)originalCorrelationPredicate.Predicate).InnerKey);

            var currentKey = BuildKeyAccess(navigation.ForeignKey.Properties, querySourceReferenceFindingExpressionTreeVisitor.QuerySourceReferenceExpression);

            // PK of the parent qsre
            var originKey = BuildKeyAccess(_queryCompilationContext.Model.FindEntityType(originQuerySource.Type).FindPrimaryKey().Properties, originQuerySource);

            // principal side of the FK relationship between parent and this collection
            var outerKey = BuildKeyAccess(navigation.ForeignKey.PrincipalKey.Properties, parentQuerySourceReferenceExpression);

            var parentQuerySource = parentQuerySourceReferenceExpression.ReferencedQuerySource;

            // ordering priority for parent:
            // - user specified orderings
            // - parent PK
            // - principal side of the FK between parent and child

            // ordering priority for child:
            // - user specified orderings on parent (from join)
            // - parent PK (from join)
            // - dependent side of the FK between parent and child
            // - customer specified orderings on child

            var parentOrderings            = new List <Ordering>();
            var exisingParentOrderByClause = _parentQueryModel.BodyClauses.OfType <OrderByClause>().LastOrDefault();

            if (exisingParentOrderByClause != null)
            {
                parentOrderings.AddRange(exisingParentOrderByClause.Orderings);
            }

            var originEntityType = _queryCompilationContext.Model.FindEntityType(originQuerySource.Type);

            foreach (var property in originEntityType.FindPrimaryKey().Properties)
            {
                TryAddPropertyToOrderings(property, originQuerySource, parentOrderings);
            }

            foreach (var property in navigation.ForeignKey.PrincipalKey.Properties)
            {
                TryAddPropertyToOrderings(property, parentQuerySourceReferenceExpression, parentOrderings);
            }

            _parentOrderings.AddRange(parentOrderings);

            // if selector contains multiple correlated collections, visiting the first one changes that collections QM (changing it's type)
            // which makes the parent QM inconsistent temporarily. QM's type is different but the CorrelateCollections method that fixes the result type
            // is not part of the QM and it's added only when the entire Selector is replaced - i.e. after all it's components have been visited

            // since when we clone the parent QM, we don't care about it's original selector anyway (it's being discarded)
            // we avoid cloning the selector in the first place and avoid all the potential problem with temporarily mismatched types of the subqueries inside
            var parentSelectClause = _parentQueryModel.SelectClause;

            _parentQueryModel.SelectClause = new SelectClause(Expression.Default(parentSelectClause.Selector.Type));

            var querySourceMapping     = new QuerySourceMapping();
            var clonedParentQueryModel = _parentQueryModel.Clone(querySourceMapping);

            _parentQueryModel.SelectClause = parentSelectClause;

            _queryCompilationContext.UpdateMapping(querySourceMapping);
            _queryCompilationContext.CloneAnnotations(querySourceMapping, clonedParentQueryModel);

            var clonedParentQuerySourceReferenceExpression
                = (QuerySourceReferenceExpression)querySourceMapping.GetExpression(parentQuerySource);

            var clonedParentQuerySource
                = clonedParentQuerySourceReferenceExpression.ReferencedQuerySource;

            var parentItemName
                = parentQuerySource.HasGeneratedItemName()
                    ? navigation.DeclaringEntityType.DisplayName()[0].ToString().ToLowerInvariant()
                    : parentQuerySource.ItemName;

            collectionQueryModel.MainFromClause.ItemName = $"{parentItemName}.{navigation.Name}";

            var collectionQuerySourceReferenceExpression
                = new QuerySourceReferenceExpression(collectionQueryModel.MainFromClause);

            var subQueryProjection = new List <Expression>();

            subQueryProjection.AddRange(parentOrderings.Select(o => CloningExpressionVisitor.AdjustExpressionAfterCloning(o.Expression, querySourceMapping)));

            var joinQuerySourceReferenceExpression
                = CreateJoinToParentQuery(
                      clonedParentQueryModel,
                      clonedParentQuerySourceReferenceExpression,
                      collectionQuerySourceReferenceExpression,
                      navigation.ForeignKey,
                      collectionQueryModel,
                      subQueryProjection);

            ApplyParentOrderings(
                parentOrderings,
                clonedParentQueryModel,
                querySourceMapping);

            LiftOrderBy(
                joinQuerySourceReferenceExpression,
                clonedParentQueryModel,
                collectionQueryModel);

            clonedParentQueryModel.SelectClause.Selector
                = Expression.New(
                      MaterializedAnonymousObject.AnonymousObjectCtor,
                      Expression.NewArrayInit(
                          typeof(object),
                          subQueryProjection.Select(e => Expression.Convert(e, typeof(object)))));

            clonedParentQueryModel.ResultTypeOverride = typeof(IQueryable <>).MakeGenericType(clonedParentQueryModel.SelectClause.Selector.Type);

            var newOriginKey = CloningExpressionVisitor
                               .AdjustExpressionAfterCloning(originKey, querySourceMapping);

            var newOriginKeyElements      = ((NewArrayExpression)(((NewExpression)newOriginKey).Arguments[0])).Expressions;
            var remappedOriginKeyElements = RemapOriginKeyExpressions(newOriginKeyElements, joinQuerySourceReferenceExpression, subQueryProjection);

            var tupleCtor = typeof(Tuple <, ,>).MakeGenericType(
                collectionQueryModel.SelectClause.Selector.Type,
                typeof(MaterializedAnonymousObject),
                typeof(MaterializedAnonymousObject)).GetConstructors().FirstOrDefault();

            var navigationParameter = Expression.Parameter(typeof(INavigation), "n");

            var correlateSubqueryMethod = _queryCompilationContext.IsAsyncQuery
                ? _correlateSubqueryAsyncMethodInfo
                : _correlateSubqueryMethodInfo;

            Expression resultCollectionFactoryExpressionBody;

            if (forceListResult ||
                navigation.ForeignKey.DeclaringEntityType.ClrType != collectionQueryModel.SelectClause.Selector.Type)
            {
                var resultCollectionType = typeof(List <>).MakeGenericType(collectionQueryModel.SelectClause.Selector.Type);
                var resultCollectionCtor = resultCollectionType.GetTypeInfo().GetDeclaredConstructor(new Type[] { });

                correlateSubqueryMethod = correlateSubqueryMethod.MakeGenericMethod(
                    collectionQueryModel.SelectClause.Selector.Type,
                    typeof(List <>).MakeGenericType(collectionQueryModel.SelectClause.Selector.Type));

                resultCollectionFactoryExpressionBody = Expression.New(resultCollectionCtor);

                trackingQuery = false;
            }
            else
            {
                correlateSubqueryMethod = correlateSubqueryMethod.MakeGenericMethod(
                    collectionQueryModel.SelectClause.Selector.Type,
                    navigation.GetCollectionAccessor().CollectionType);

                resultCollectionFactoryExpressionBody
                    = Expression.Convert(
                          Expression.Call(
                              Expression.Call(_getCollectionAccessorMethodInfo, navigationParameter),
                              _createCollectionMethodInfo),
                          navigation.GetCollectionAccessor().CollectionType);
            }

            var resultCollectionFactoryExpression = Expression.Lambda(
                resultCollectionFactoryExpressionBody,
                navigationParameter);

            collectionQueryModel.SelectClause.Selector
                = Expression.New(
                      tupleCtor,
                      new Expression[]
            {
                collectionQueryModel.SelectClause.Selector,
                currentKey,
                Expression.New(
                    MaterializedAnonymousObject.AnonymousObjectCtor,
                    Expression.NewArrayInit(
                        typeof(object),
                        remappedOriginKeyElements))
            });

            var collectionModelSelectorType = collectionQueryModel.SelectClause.Selector.Type;

            // Enumerable or OrderedEnumerable
            collectionQueryModel.ResultTypeOverride = collectionQueryModel.BodyClauses.OfType <OrderByClause>().Any()
                ? typeof(IOrderedEnumerable <>).MakeGenericType(collectionModelSelectorType)
                : typeof(IEnumerable <>).MakeGenericType(collectionModelSelectorType);

            var lambda = (Expression)Expression.Lambda(new SubQueryExpression(collectionQueryModel));

            if (_queryCompilationContext.IsAsyncQuery)
            {
                lambda = Expression.Convert(
                    lambda,
                    typeof(Func <>).MakeGenericType(
                        typeof(IAsyncEnumerable <>).MakeGenericType(collectionModelSelectorType)));
            }

            // since we cloned QM, we need to check if it's query sources require materialization (e.g. TypeIs operation for InMemory)
            _queryCompilationContext.FindQuerySourcesRequiringMaterialization(_queryModelVisitor, collectionQueryModel);

            var correlationPredicate = CreateCorrelationPredicate(navigation);

            var arguments = new List <Expression>
            {
                Expression.Constant(correlatedCollectionIndex),
                Expression.Constant(navigation),
                resultCollectionFactoryExpression,
                outerKey,
                Expression.Constant(trackingQuery),
                lambda,
                correlationPredicate
            };

            if (_queryCompilationContext.IsAsyncQuery)
            {
                arguments.Add(_cancellationTokenParameter);
            }

            var result = Expression.Call(
                Expression.Property(
                    EntityQueryModelVisitor.QueryContextParameter,
                    nameof(QueryContext.QueryBuffer)),
                correlateSubqueryMethod,
                arguments);

            if (_queryCompilationContext.IsAsyncQuery)
            {
                var taskResultExpression = new TaskBlockingExpressionVisitor().Visit(result);

                return(taskResultExpression);
            }

            return(result);
        }
        /// <summary>
        ///     This API supports the Entity Framework Core infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        protected override void FlattenSubQuery(
            SubQueryExpression subQueryExpression,
            IFromClause fromClause,
            QueryModel queryModel,
            int destinationIndex)
        {
            var subQueryModel = subQueryExpression.QueryModel;

            VisitQueryModel(subQueryModel);

            // no groupby and no distinct
            var emptyQueryModelWithFlattenableResultOperatorInSubquery
                = !queryModel.BodyClauses.Any() &&
                  subQueryModel.ResultOperators.All(
                      ro => ro is CastResultOperator ||
                      ro is DefaultIfEmptyResultOperator ||
                      ro is ExceptResultOperator ||
                      ro is OfTypeResultOperator ||
                      ro is ReverseResultOperator ||
                      ro is SkipResultOperator ||
                      ro is TakeResultOperator);

            // we can lift distinct however if the outer query has result operator that doesn't care about having correct element count
            var emptyQueryModelWithResultOperatorThatIgnoresElementCountAndDistinctInSubquery
                = !queryModel.BodyClauses.Any() &&
                  subQueryModel.ResultOperators.Any(ro => ro is DistinctResultOperator) &&
                  queryModel.ResultOperators.Any(
                      ro => ro is ContainsResultOperator ||
                      ro is AnyResultOperator ||
                      ro is AllResultOperator ||
                      ro is MinResultOperator ||
                      ro is MaxResultOperator);

            var subqueryInMainClauseWithoutResultOperatorsProjectingItsMainClause
                = fromClause is MainFromClause &&
                  !subQueryModel.ResultOperators.Any() &&
                  subQueryModel.SelectClause.Selector is QuerySourceReferenceExpression subquerySelectorsQsre &&
                  subquerySelectorsQsre.ReferencedQuerySource == subQueryModel.MainFromClause;

            if (subQueryModel.ResultOperators.All(ro => ro is CastResultOperator) &&
                !subQueryModel.BodyClauses.Any(bc => bc is OrderByClause) &&
                subQueryModel.SelectClause.Selector.NodeType != ExpressionType.MemberInit &&
                subQueryModel.SelectClause.Selector.NodeType != ExpressionType.New ||
                queryModel.IsIdentityQuery() &&
                !queryModel.ResultOperators.Any() ||
                emptyQueryModelWithFlattenableResultOperatorInSubquery ||
                emptyQueryModelWithResultOperatorThatIgnoresElementCountAndDistinctInSubquery ||
                subqueryInMainClauseWithoutResultOperatorsProjectingItsMainClause)
            {
                var querySourceMapping  = new QuerySourceMapping();
                var clonedSubQueryModel = subQueryModel.Clone(querySourceMapping);
                UpdateQueryAnnotations(subQueryModel, querySourceMapping);
                _queryCompilationContext.UpdateMapping(querySourceMapping);

                var innerMainFromClause  = clonedSubQueryModel.MainFromClause;
                var isGeneratedNameOuter = fromClause.HasGeneratedItemName();

                var itemName = innerMainFromClause.HasGeneratedItemName() &&
                               !isGeneratedNameOuter
                    ? fromClause.ItemName
                    : innerMainFromClause.ItemName;

                var fromClauseData
                    = new FromClauseData(
                          itemName, innerMainFromClause.ItemType, innerMainFromClause.FromExpression);

                fromClause.CopyFromSource(fromClauseData);

                var newExpression           = clonedSubQueryModel.SelectClause.Selector;
                var newExpressionTypeInfo   = newExpression.Type.GetTypeInfo();
                var castResultOperatorTypes = clonedSubQueryModel.ResultOperators.OfType <CastResultOperator>().Select(cre => cre.CastItemType).ToList();
                var type = castResultOperatorTypes.LastOrDefault(t => newExpressionTypeInfo.IsAssignableFrom(t.GetTypeInfo()));

                if (type != null &&
                    type != newExpression.Type)
                {
                    newExpression = Expression.Convert(newExpression, type);
                }

                UpdateQuerySourceMapping(
                    queryModel,
                    fromClause,
                    newExpression);

                InsertBodyClauses(clonedSubQueryModel.BodyClauses, queryModel, destinationIndex);

                foreach (var resultOperator in clonedSubQueryModel.ResultOperators.Where(ro => !(ro is CastResultOperator)).Reverse())
                {
                    queryModel.ResultOperators.Insert(0, resultOperator);
                }

                UpdateQuerySourceMapping(
                    queryModel,
                    innerMainFromClause,
                    new QuerySourceReferenceExpression(fromClause));
            }
        }