in Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs [1791:2042]
private static Collection VisitGroupBy(Type returnElementType, ReadOnlyCollection<Expression> arguments, TranslationContext context)
{
if (arguments.Count != 3)
{
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.InvalidArgumentsCount, LinqMethods.GroupBy, 3, arguments.Count));
}
// Key Selector handling
// First argument is input, second is key selector and third is value selector
LambdaExpression keySelectorLambda = Utilities.GetLambda(arguments[1]);
Collection collection = new Collection("Group By");
context.CurrentQuery.GroupByParameter = new FromParameterBindings();
SqlGroupByClause groupby;
ParameterExpression parameterExpression;
switch (keySelectorLambda.Body.NodeType)
{
case ExpressionType.Parameter:
case ExpressionType.Call:
case ExpressionType.MemberAccess:
{
// bind the parameters in the value selector to the current input
foreach (ParameterExpression par in Utilities.GetLambda(arguments[2]).Parameters)
{
context.PushParameter(par, context.CurrentSubqueryBinding.ShouldBeOnNewQuery);
}
//Current GroupBy doesn't allow subquery, so we need to visit non subquery scalar lambda
SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarLambda(keySelectorLambda, context);
// The group by clause don't need to handle the value selector, so adding the clause to the uery now.
groupby = SqlGroupByClause.Create(keySelectorFunc);
parameterExpression = context.GenerateFreshParameter(returnElementType, keySelectorFunc.ToString(), includeSuffix: false);
break;
}
case ExpressionType.New:
{
// bind the parameters in the key selector to the current input - in this case, the value selector key is being substituted by the key selector
foreach (ParameterExpression par in Utilities.GetLambda(arguments[1]).Parameters)
{
context.PushParameter(par, context.CurrentSubqueryBinding.ShouldBeOnNewQuery);
}
NewExpression newExpression = (NewExpression)keySelectorLambda.Body;
if (newExpression.Members == null)
{
throw new DocumentQueryException(ClientResources.ConstructorInvocationNotSupported);
}
ReadOnlyCollection<Expression> newExpressionArguments = newExpression.Arguments;
List<SqlScalarExpression> keySelectorFunctions = new List<SqlScalarExpression>();
for (int i = 0; i < newExpressionArguments.Count; i++)
{
//Current GroupBy doesn't allow subquery, so we need to visit non subquery scalara
SqlScalarExpression keySelectorFunc = ExpressionToSql.VisitNonSubqueryScalarExpression(newExpressionArguments[i], context);
keySelectorFunctions.Add(keySelectorFunc);
}
groupby = SqlGroupByClause.Create(keySelectorFunctions.ToImmutableArray());
parameterExpression = context.GenerateFreshParameter(returnElementType, keySelectorFunctions.ToString(), includeSuffix: false);
break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, keySelectorLambda.Body.NodeType));
}
// The group by clause don't need to handle the value selector, so adding the clause to the qery now.
context.CurrentQuery = context.CurrentQuery.AddGroupByClause(groupby, context);
// Bind the alias
Binding binding = new Binding(parameterExpression, collection.inner, isInCollection: false, isInputParameter: true);
context.CurrentQuery.GroupByParameter.Add(binding);
// The alias for the key in the value selector lambda is the first arguemt lambda - we bound it to the parameter expression, which already has substitution
ParameterExpression valueSelectorKeyExpressionAlias = Utilities.GetLambda(arguments[2]).Parameters[0];
context.GroupByKeySubstitution.AddSubstitution(valueSelectorKeyExpressionAlias, parameterExpression);
// Value Selector Handingling
// Translate the body of the value selector lambda
Expression valueSelectorExpression = Utilities.GetLambda(arguments[2]).Body;
// The value selector function needs to be either a MethodCall or an AnonymousType
switch (valueSelectorExpression.NodeType)
{
case ExpressionType.MemberAccess:
{
MemberExpression memberAccessExpression = (MemberExpression)valueSelectorExpression;
if (memberAccessExpression.Expression.NodeType == ExpressionType.Parameter)
{
// Look up the object of the expression to see if it is the key
ParameterExpression memberAccessObject = (ParameterExpression)memberAccessExpression.Expression;
Expression subst = context.GroupByKeySubstitution.Lookup(memberAccessObject);
if (subst != null)
{
// If there is a match, we construct a new Member Access expression with the substituted expression and visit it to create a select clause
MemberExpression newMemberAccessExpression = memberAccessExpression.Update(keySelectorLambda.Body);
SqlScalarExpression selectExpression = ExpressionToSql.VisitMemberAccess(newMemberAccessExpression, context);
SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
}
}
break;
}
case ExpressionType.Constant:
{
ConstantExpression constantExpression = (ConstantExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitConstant(constantExpression, context);
SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.Parameter:
{
ParameterExpression parameterValueExpression = (ParameterExpression)valueSelectorExpression;
SqlScalarExpression selectExpression = ExpressionToSql.VisitParameter(parameterValueExpression, context);
SqlSelectSpec sqlSpec = SqlSelectValueSpec.Create(selectExpression);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.Call:
{
// Single Value Selector
MethodCallExpression methodCallExpression = (MethodCallExpression)valueSelectorExpression;
SqlSelectClause select = ExpressionToSql.VisitGroupByAggregateMethodCall(methodCallExpression, context);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
case ExpressionType.New:
{
// Add select item clause at the end of this method
NewExpression newExpression = (NewExpression)valueSelectorExpression;
if (newExpression.Members == null)
{
throw new DocumentQueryException(ClientResources.ConstructorInvocationNotSupported);
}
// Get the list of items and the bindings
ReadOnlyCollection<Expression> newExpressionArguments = newExpression.Arguments;
ReadOnlyCollection<MemberInfo> newExpressionMembers = newExpression.Members;
SqlSelectItem[] selectItems = new SqlSelectItem[newExpressionArguments.Count];
for (int i = 0; i < newExpressionArguments.Count; i++)
{
MemberInfo member = newExpressionMembers[i];
string memberName = member.GetMemberName(context);
SqlIdentifier alias = SqlIdentifier.Create(memberName);
Expression arg = newExpressionArguments[i];
switch (arg.NodeType)
{
case ExpressionType.Constant:
{
SqlScalarExpression selectExpression = ExpressionToSql.VisitConstant((ConstantExpression)arg, context);
SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
case ExpressionType.Parameter:
{
SqlScalarExpression selectExpression = ExpressionToSql.VisitParameter((ParameterExpression)arg, context);
SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
case ExpressionType.Call:
{
SqlSelectClause selectClause = ExpressionToSql.VisitGroupByAggregateMethodCall((MethodCallExpression)arg, context);
SqlScalarExpression selectExpression = ((SqlSelectValueSpec)selectClause.SelectSpec).Expression;
SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
break;
}
case ExpressionType.MemberAccess:
{
MemberExpression memberAccessExpression = (MemberExpression)arg;
if (memberAccessExpression.Expression.NodeType == ExpressionType.Parameter)
{
// Look up the object of the expression to see if it is the key
ParameterExpression memberAccessObject = (ParameterExpression)memberAccessExpression.Expression;
Expression subst = context.GroupByKeySubstitution.Lookup(memberAccessObject);
if (subst != null)
{
// If there is a match, we construct a new Member Access expression with the substituted expression and visit it to create a select clause
MemberExpression newMemberAccessExpression = memberAccessExpression.Update(keySelectorLambda.Body); /*System.Linq.Expressions.Expression.Field(subst, memberAccessExpression.Member.Name);*/
SqlScalarExpression selectExpression = ExpressionToSql.VisitMemberAccess(newMemberAccessExpression, context);
SqlSelectItem prop = SqlSelectItem.Create(selectExpression, alias);
selectItems[i] = prop;
}
}
break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, arg.NodeType));
}
}
SqlSelectListSpec sqlSpec = SqlSelectListSpec.Create(selectItems);
SqlSelectClause select = SqlSelectClause.Create(sqlSpec, null);
context.CurrentQuery = context.CurrentQuery.AddSelectClause(select, context);
break;
}
default:
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.ExpressionTypeIsNotSupported, valueSelectorExpression.NodeType));
}
// Pop the correct number of items off the parameter stack
switch (keySelectorLambda.Body.NodeType)
{
case ExpressionType.Parameter:
case ExpressionType.Call:
case ExpressionType.MemberAccess:
{
foreach (ParameterExpression param in Utilities.GetLambda(arguments[2]).Parameters)
{
context.PopParameter();
}
break;
}
case ExpressionType.New:
{
//bind the parameters in the value selector to the current input
foreach (ParameterExpression param in Utilities.GetLambda(arguments[1]).Parameters)
{
context.PopParameter();
}
break;
}
default:
break;
}
return collection;
}