Ejemplo n.º 1
0
        /// <summary>
        /// Clones this <see cref="QueryModel"/>, returning a new <see cref="QueryModel"/> equivalent to this instance, but with its clauses being
        /// clones of this instance's clauses. Any <see cref="QuerySourceReferenceExpression"/> in the cloned clauses that points back to another clause
        /// in  this <see cref="QueryModel"/> (including its subqueries) is adjusted to point to the respective clones in the cloned
        /// <see cref="QueryModel"/>. Any subquery nested in the <see cref="QueryModel"/> is also cloned.
        /// </summary>
        /// <param name="querySourceMapping">The <see cref="QuerySourceMapping"/> defining how to adjust instances of
        /// <see cref="QuerySourceReferenceExpression"/> in the cloned <see cref="QueryModel"/>. If there is a <see cref="QuerySourceReferenceExpression"/>
        /// that points out of the <see cref="QueryModel"/> being cloned, specify its replacement via this parameter. At the end of the cloning process,
        /// this object maps all the clauses in this original <see cref="QueryModel"/> to the clones created in the process.
        /// </param>
        public QueryModel Clone(QuerySourceMapping querySourceMapping)
        {
            ArgumentUtility.CheckNotNull("querySourceMapping", querySourceMapping);

            var cloneContext      = new CloneContext(querySourceMapping);
            var queryModelBuilder = new QueryModelBuilder();

            queryModelBuilder.AddClause(MainFromClause.Clone(cloneContext));
            foreach (var bodyClause in BodyClauses)
            {
                queryModelBuilder.AddClause(bodyClause.Clone(cloneContext));
            }
            queryModelBuilder.AddClause(SelectClause.Clone(cloneContext));

            foreach (var resultOperator in ResultOperators)
            {
                var resultOperatorClone = resultOperator.Clone(cloneContext);
                queryModelBuilder.AddResultOperator(resultOperatorClone);
            }

            var clone = queryModelBuilder.Build();
            var cloningExpressionVisitor = new CloningExpressionVisitor(cloneContext.QuerySourceMapping);

            clone.TransformExpressions(cloningExpressionVisitor.Visit);
            clone.ResultTypeOverride = ResultTypeOverride;
            return(clone);
        }
            private static void ApplyParentOrderings(
                IEnumerable <Ordering> parentOrderings,
                QueryModel queryModel,
                QuerySourceMapping querySourceMapping,
                bool reverseOrdering)
            {
                var orderByClause = queryModel.BodyClauses.OfType <OrderByClause>().LastOrDefault();

                if (orderByClause == null)
                {
                    queryModel.BodyClauses.Add(orderByClause = new OrderByClause());
                }

                foreach (var ordering in parentOrderings)
                {
                    var newExpression
                        = CloningExpressionVisitor
                          .AdjustExpressionAfterCloning(ordering.Expression, querySourceMapping);

                    if (newExpression is MethodCallExpression methodCallExpression &&
                        methodCallExpression.Method.IsEFPropertyMethod())
                    {
                        newExpression
                            = new NullConditionalExpression(
                                  methodCallExpression.Arguments[0],
                                  methodCallExpression.Arguments[0],
                                  methodCallExpression);
                    }

                    orderByClause.Orderings
                    .Add(new Ordering(newExpression, ordering.OrderingDirection));
                }

                if (reverseOrdering)
                {
                    foreach (var ordering in orderByClause.Orderings)
                    {
                        ordering.OrderingDirection
                            = ordering.OrderingDirection == OrderingDirection.Asc
                                ? OrderingDirection.Desc
                                : OrderingDirection.Asc;
                    }
                }
            }
Ejemplo n.º 3
0
            private static void LiftOrderBy(
                IQuerySource querySource,
                Expression targetExpression,
                QueryModel fromQueryModel,
                QueryModel toQueryModel,
                List <Expression> subQueryProjection)
            {
                var canRemove
                    = !fromQueryModel.ResultOperators
                      .Any(r => r is SkipResultOperator || r is TakeResultOperator);

                foreach (var orderByClause
                         in fromQueryModel.BodyClauses.OfType <OrderByClause>().ToArray())
                {
                    var outerOrderByClause = new OrderByClause();

                    foreach (var ordering in orderByClause.Orderings)
                    {
                        int projectionIndex;
                        var orderingExpression = ordering.Expression;
                        if (ordering.Expression.RemoveConvert() is NullConditionalExpression nullConditionalExpression)
                        {
                            orderingExpression = nullConditionalExpression.AccessOperation;
                        }

                        QuerySourceReferenceExpression orderingExpressionQsre = null;
                        string orderingExpressionName = null;
                        if (orderingExpression.RemoveConvert() is MemberExpression memberExpression &&
                            memberExpression.Expression.RemoveConvert() is QuerySourceReferenceExpression memberQsre &&
                            memberQsre.ReferencedQuerySource == querySource)
                        {
                            orderingExpressionQsre = memberQsre;
                            orderingExpressionName = memberExpression.Member.Name;
                        }

                        if (orderingExpression.RemoveConvert() is MethodCallExpression methodCallExpression &&
                            methodCallExpression.IsEFProperty() &&
                            methodCallExpression.Arguments[0].RemoveConvert() is QuerySourceReferenceExpression methodCallQsre &&
                            methodCallQsre.ReferencedQuerySource == querySource)
                        {
                            orderingExpressionQsre = methodCallQsre;
                            orderingExpressionName = (string)((ConstantExpression)methodCallExpression.Arguments[1]).Value;
                        }

                        if (orderingExpressionQsre != null && orderingExpressionName != null)
                        {
                            projectionIndex
                                = subQueryProjection
                                  .FindIndex(
                                      e =>
                            {
                                var expressionWithoutConvert = e.RemoveConvert();
                                var projectionExpression     = (expressionWithoutConvert as NullConditionalExpression)?.AccessOperation
                                                               ?? expressionWithoutConvert;

                                if (projectionExpression is MethodCallExpression methodCall &&
                                    methodCall.Method.IsEFPropertyMethod())
                                {
                                    var properyQsre  = (QuerySourceReferenceExpression)methodCall.Arguments[0].RemoveConvert();
                                    var propertyName = (string)((ConstantExpression)methodCall.Arguments[1]).Value;

                                    return(properyQsre.ReferencedQuerySource == orderingExpressionQsre.ReferencedQuerySource &&
                                           propertyName == orderingExpressionName);
                                }

                                if (projectionExpression is MemberExpression projectionMemberExpression)
                                {
                                    var projectionMemberQsre = (QuerySourceReferenceExpression)projectionMemberExpression.Expression.RemoveConvert();

                                    return(projectionMemberQsre.ReferencedQuerySource == orderingExpressionQsre.ReferencedQuerySource &&
                                           projectionMemberExpression.Member.Name == orderingExpressionName);
                                }

                                return(false);
                            });
                        }
                        else
                        {
                            projectionIndex
                                = subQueryProjection
                                  // Do NOT use orderingExpression variable here
                                  .FindIndex(e => _expressionEqualityComparer.Equals(e.RemoveConvert(), ordering.Expression.RemoveConvert()));
                        }

                        if (projectionIndex == -1)
                        {
                            projectionIndex = subQueryProjection.Count;

                            subQueryProjection.Add(
                                Expression.Convert(
                                    // Workaround re-linq#RMLNQ-111 - When this is fixed the Clone can go away
                                    CloningExpressionVisitor.AdjustExpressionAfterCloning(
                                        ordering.Expression,
                                        new QuerySourceMapping()),
                                    typeof(object)));
                        }

                        var newExpression
                            = Expression.Call(
                                  targetExpression,
                                  AnonymousObject.GetValueMethodInfo,
                                  Expression.Constant(projectionIndex));

                        outerOrderByClause.Orderings
                        .Add(new Ordering(newExpression, ordering.OrderingDirection));
                    }

                    toQueryModel.BodyClauses.Add(outerOrderByClause);

                    if (canRemove)
                    {
                        fromQueryModel.BodyClauses.Remove(orderByClause);
                    }
                }
            }
Ejemplo n.º 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);
        }
Ejemplo n.º 5
0
    /// <summary>
    /// Clones this <see cref="QueryModel"/>, returning a new <see cref="QueryModel"/> equivalent to this instance, but with its clauses being
    /// clones of this instance's clauses. Any <see cref="QuerySourceReferenceExpression"/> in the cloned clauses that points back to another clause 
    /// in  this <see cref="QueryModel"/> (including its subqueries) is adjusted to point to the respective clones in the cloned 
    /// <see cref="QueryModel"/>. Any subquery nested in the <see cref="QueryModel"/> is also cloned.
    /// </summary>
    /// <param name="querySourceMapping">The <see cref="QuerySourceMapping"/> defining how to adjust instances of 
    /// <see cref="QuerySourceReferenceExpression"/> in the cloned <see cref="QueryModel"/>. If there is a <see cref="QuerySourceReferenceExpression"/>
    /// that points out of the <see cref="QueryModel"/> being cloned, specify its replacement via this parameter. At the end of the cloning process,
    /// this object maps all the clauses in this original <see cref="QueryModel"/> to the clones created in the process.
    /// </param>
    public QueryModel Clone (QuerySourceMapping querySourceMapping)
    {
      ArgumentUtility.CheckNotNull ("querySourceMapping", querySourceMapping);

      var cloneContext = new CloneContext (querySourceMapping);
      var queryModelBuilder = new QueryModelBuilder();

      queryModelBuilder.AddClause (MainFromClause.Clone (cloneContext));
      foreach (var bodyClause in BodyClauses)
        queryModelBuilder.AddClause (bodyClause.Clone (cloneContext));
      queryModelBuilder.AddClause (SelectClause.Clone (cloneContext));

      foreach (var resultOperator in ResultOperators)
      {
        var resultOperatorClone = resultOperator.Clone (cloneContext);
        queryModelBuilder.AddResultOperator (resultOperatorClone);
      }

      var clone = queryModelBuilder.Build ();
      var cloningExpressionVisitor = new CloningExpressionVisitor (cloneContext.QuerySourceMapping);
      clone.TransformExpressions (cloningExpressionVisitor.Visit);
      clone.ResultTypeOverride = ResultTypeOverride;
      return clone;
    }