protected virtual Expression BindGroupBy(Expression source, LambdaExpression keySelector, LambdaExpression elementSelector, LambdaExpression resultSelector) { ProjectionExpression projection = this.VisitSequence(source); this.map[keySelector.Parameters[0]] = projection.Projector; Expression keyExpr = this.Visit(keySelector.Body); Expression elemExpr = projection.Projector; if (elementSelector != null) { this.map[elementSelector.Parameters[0]] = projection.Projector; elemExpr = this.Visit(elementSelector.Body); } // Use ProjectColumns to get group-by expressions from key expression ProjectedColumns keyProjection = this.ProjectColumns(keyExpr, projection.Source.Alias, projection.Source.Alias); IEnumerable <Expression> groupExprs = keyProjection.Columns.Select(c => c.Expression); // make duplicate of source query as basis of element subquery by visiting the source again ProjectionExpression subqueryBasis = this.VisitSequence(source); // recompute key columns for group expressions relative to subquery (need these for doing the correlation predicate) this.map[keySelector.Parameters[0]] = subqueryBasis.Projector; Expression subqueryKey = this.Visit(keySelector.Body); // use same projection trick to get group-by expressions based on subquery ProjectedColumns subqueryKeyPC = this.ProjectColumns(subqueryKey, subqueryBasis.Source.Alias, subqueryBasis.Source.Alias); IEnumerable <Expression> subqueryGroupExprs = subqueryKeyPC.Columns.Select(c => c.Expression); Expression subqueryCorrelation = this.BuildPredicateWithNullsEqual(subqueryGroupExprs, groupExprs); // compute element based on duplicated subquery Expression subqueryElemExpr = subqueryBasis.Projector; if (elementSelector != null) { this.map[elementSelector.Parameters[0]] = subqueryBasis.Projector; subqueryElemExpr = this.Visit(elementSelector.Body); } // build subquery that projects the desired element string elementAlias = this.GetNextAlias(); ProjectedColumns elementPC = this.ProjectColumns(subqueryElemExpr, elementAlias, subqueryBasis.Source.Alias); ProjectionExpression elementSubquery = new ProjectionExpression( new SelectExpression(TypeSystem.GetSequenceType(subqueryElemExpr.Type), elementAlias, elementPC.Columns, subqueryBasis.Source, subqueryCorrelation), elementPC.Projector ); string alias = this.GetNextAlias(); // make it possible to tie aggregates back to this group-by GroupByInfo info = new GroupByInfo(alias, elemExpr); this.groupByMap.Add(elementSubquery, info); Expression resultExpr; if (resultSelector != null) { Expression saveGroupElement = this.currentGroupElement; this.currentGroupElement = elementSubquery; // compute result expression based on key & element-subquery this.map[resultSelector.Parameters[0]] = keyProjection.Projector; this.map[resultSelector.Parameters[1]] = elementSubquery; resultExpr = this.Visit(resultSelector.Body); this.currentGroupElement = saveGroupElement; } else { // result must be IGrouping<K,E> resultExpr = Expression.New( typeof(Grouping <,>).MakeGenericType(keyExpr.Type, subqueryElemExpr.Type).GetConstructors()[0], new Expression[] { keyExpr, elementSubquery } ); } ProjectedColumns pc = this.ProjectColumns(resultExpr, alias, projection.Source.Alias); // make it possible to tie aggregates back to this group-by Expression projectedElementSubquery = ((NewExpression)pc.Projector).Arguments[1]; this.groupByMap.Add(projectedElementSubquery, info); return(new ProjectionExpression( new SelectExpression(TypeSystem.GetSequenceType(resultExpr.Type), alias, pc.Columns, projection.Source, null, null, groupExprs), pc.Projector )); }