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