2

When I write a Group By in query expression syntax, the compiler automatically picks Enumerable.GroupBy as my intended tareget method and I get an IEnumerable back instead of an IQueryable. That means a subsequent g.Sum call (inside of my Select) expects a Func(Of TSource, int) instead of an Expression(Of Func(Of TSource, int)). Is there a way to force the Group By to use Queryable.GroupBy instead and give me back an IQueryable?

Contrived Sample Code

Dim myQuery = From x In DataSource.Items
              Group By x.Key Into g = Group
              Select New With {
                  .Key = Key,
                  .RedItems = g.Sum(ItemsOfColor(Colors.Red)) '<== invalid because g.Sum expects a lambda
              }

Private Function PurpleItems(color As Integer) As Expression(Of Func(Of Item, Integer))
    Return Function(item) If(item.Color = color, 1, 0)
End Function

Why would I want to do this? The compiler automatically converts between a lambda and an expression based on the target variable type (ie, both Dim f As Func(Of String, Integer) = Function(x) x.Length() and Dim e As Expression(Of Func(Of String, Integer)) = Function(x) x.Length() are valid) so there is no noticable difference in the code between an IEnumerable and IQueryable.

The problem is, LINQ to Entities (and I assume other db backed LINQ implementations) relies on expression trees to translate into SQL. That means the IEnumerable lambda version will not work against an IDbSet as I found in this old question.

Community
  • 1
  • 1
just.another.programmer
  • 8,579
  • 8
  • 51
  • 90

1 Answers1

2

The problem is that Queryable.GroupBy() returns IQueryable<IGrouping<TKey, TSource>>, where IGrouping<TKey, TSource> implemens IEnumerable<TSource>, but not IQueryable<TSource>.

And I believe your code wouldn't work anyway, because ItemsOfColor() wouldn't be actually called. Instead, the EF would get an expression that calls ItemsOfColor(). And since it doesn't know that method, it would throw an exception.

svick
  • 236,525
  • 50
  • 385
  • 514
  • Why should the type of `IGrouping` matter if I have an `IQueryable>`? Also, you're right, it does make a MethodCallExpression. Any way to force the method to call immediately (something like a C macro)? – just.another.programmer Apr 12 '13 at 08:05
  • @just.another.programmer Because `g` in your code represents that `IGrouping`, not `IQueryable` (though VB seems to do something weird, so `g` is actually just `IEnumerable`). – svick Apr 12 '13 at 10:32
  • That makes sense, but how does the compiler know to translate everything into expressions if it's an `IEnumerable` not an `IQueryable`? (like the `MethodCallExpression` causing my problem here, or the the `LambdaExpression` it makes when you pass a lambda)? – just.another.programmer Apr 13 '13 at 18:45
  • @just.another.programmer Because that `IEnumerable` is used inside an expression, so it doesn't matter that it's itself just `IEnumerable`, since the collection that contains it is `IQueryable`. – svick Apr 13 '13 at 19:14
  • At *run time* I could see that making a difference, but AFAIK the translation to `Expression` happens at *compile time*. When the compiler sees an `IEnumerable` calling a `Sum`, it expects a lambda so there should be no translation. – just.another.programmer Apr 13 '13 at 19:19
  • 1
    @just.another.programmer I'm talking just about compile time types. And when compiler sees the call to `Enumerable.Sum()` with a lambda inside which itself is *inside another lambda* and that outer is compiled as an expression, the inner lambda is also compiled as an expression. – svick Apr 13 '13 at 19:23
  • Tracking that must make for an amazingly complex compilation process. Thanks. – just.another.programmer Apr 13 '13 at 19:24