Reputation: 975
What I need is to represent this query via Linq.Expressions:
db.Documents.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount) });
Here is what I have so far:
IQueryable<Document> data = db.Documents;
ParameterExpression pe = Expression.Parameter(typeof(Document), "doc");
Expression groupBy = Expression.Call(
typeof(Queryable),
"GroupBy",
new Type[] { typeof(Document), typeof(int) },
data.Expression,
Expression.Lambda(Expression.Constant(1), pe));
ParameterExpression peg = Expression.Parameter(typeof(IGrouping<int, Document>), "group");
Expression select = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(IGrouping<int, Document>), typeof(int) },
groupBy,
Expression.Lambda(Expression.Property(peg, "Key"), peg));
foreach (var item in data.Provider.CreateQuery(select)) { ... }
This was implementation of:
db.Documents.GroupBy(a => 1).Select(b => b.Key });
And it works perfectly. Now, I want to aggregate a sum instead of accessing the key of group.
That is where it gets tricky for me. I was thinking something like this:
ParameterExpression pe1 = Expression.Parameter(typeof(Document), "other");
Expression sum = Expression.Call(
typeof(Queryable),
"Sum",
new Type[] { typeof(Document) },
peg,
Expression.Lambda(Expression.Property(pe1, "Amount"), pe1));
Also, for Sum function in
...b.Sum(c => c.Amount)
Intellisense gives signature:
IEnumerable<Document>.Sum<Document>(Func<Document, decimal> selector)
While for:
db.Documents.Sum(a => a.Amount)
I get:
IQueryable<Document>.Sum<Document>(Expression<Func<Document, decimal>> selector)
Selector is Func in one version and Expression in other. I don't know how to handle Func in Linq Expressions. Maybe Intellisense is wrong?
Expression for source of aggregation is my biggest issue. By looking at:
...b.Sum(c => c.Amount)
i would presume that b should be IGrouping (ParameterExpression of 'select'), and that should be the source for Sum, but that won't compile. I don't know what else to try?
Here is how last select expression should look like:
Expression Select = Expression.Call(
typeof(Queryable),
"Select",
new Type[] { typeof(IGrouping<int, Document>), typeof(decimal?) },
GroupBy,
Expression.Lambda(sum, peg));
But I can't even reach this point, because of the failed 'sum' expression.
Any pointers would be appreciated.
Regards,
Upvotes: 2
Views: 1360
Reputation: 205539
The Intellisense is ok. Let see:
db.Documents.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount) });
(1) db.Documents
type is IQueryable<Document>
(2) a
type is Document
(3) db.Documents.GroupBy(a => 1)
type is IQueryable<IGrouping<int, Document>>
(4) b
type is IGrouping<int, Document>
, which in turn is IEnumerable<Document>
(5) c
type is Document
which also means that GroupBy
and Select
methods are from Queryable
while Sum
is from Enumerable
.
What about how to distinguish between Func<...>
and Expression<Func<...>>
inside the MethodCall
expressions, the rule is simple. In both cases you use Expression.Lambda<Func<...>>
to create Expression<Func<...>>
, and then if the call requires Func<...>
you pass it directly, and if the method expects Expression<Func<...>>
then you wrap it with Expression.Quote
.
With that being said, let build the sample query expression:
var query = db.Documents.AsQueryable();
// query.GroupBy(a => 1)
var a = Expression.Parameter(typeof(Document), "a");
var groupKeySelector = Expression.Lambda(Expression.Constant(1), a);
var groupByCall = Expression.Call(typeof(Queryable), "GroupBy",
new Type[] { a.Type, groupKeySelector.Body.Type },
query.Expression, Expression.Quote(groupKeySelector));
// c => c.Amount
var c = Expression.Parameter(typeof(Document), "c");
var sumSelector = Expression.Lambda(Expression.PropertyOrField(c, "Amount"), c);
// b => b.Sum(c => c.Amount)
var b = Expression.Parameter(groupByCall.Type.GetGenericArguments().Single(), "b");
var sumCall = Expression.Call(typeof(Enumerable), "Sum",
new Type[] { c.Type },
b, sumSelector);
// query.GroupBy(a => 1).Select(b => b.Sum(c => c.Amount))
var selector = Expression.Lambda(sumCall, b);
var selectCall = Expression.Call(typeof(Queryable), "Select",
new Type[] { b.Type, selector.Body.Type },
groupByCall, Expression.Quote(selector));
// selectCall is our expression, let test it
var result = query.Provider.CreateQuery(selectCall);
Upvotes: 5