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; } } }
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); } } }
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); }