10

For educational purposes I tried to convert the following Linq expression from the book "Linq in action" into VB.net

Original C#

var list = 
  from book in SampleData.Books
  group book by new { book.Publisher.Name, book.Subject }
    into grouping
  select new {
    Publisher = grouping.Key.Publisher,
    Subject = grouping.Key.Subject,
    Book = grouping
  };

My attempt:

Dim list = _books.GroupBy(Function(book) New With {.Publisher = book.Publisher.Name,
                                                    book.Subject}).
                  Select(Function(grouping) New With {.Publisher = grouping.Key.Publisher,
                                                      .Subject = grouping.Key.Subject,
                                                      .Books = grouping})

For Each item In list
  Console.WriteLine("Publisher:" & item.Publisher & ", Subject:" & item.Subject)
  For Each Book In item.Books
    Console.WriteLine("  " & Book.Title)
  Next
Next

This leads to the following output:

Publisher:FunBooks, Subject:Fun  
  Funny Stories  
Publisher:Joe Publishing, Subject:Work  
  LINQ rules  
Publisher:Joe Publishing, Subject:Work  
  C# on rails  
Publisher:Joe Publishing, Subject:Fun  
  All your base are belong to us  
Publisher:FunBooks, Subject:Fun  
  Bonjour mon Amour  

I expected, that the books "LINQ rules" and "C# on rails" are grouped as well as the books "Funny Stories" and "Bonjour mon Amour" because they have the same Publisher and Subject. My anonymous key consists a new object of two simple strings.

I already tried to search in SO, but other (or) answers do not solve my problem. Even some code translators like telerik or carlosag are no help in this case.

Community
  • 1
  • 1
Markus
  • 4,289
  • 3
  • 38
  • 49

2 Answers2

7

This is the problem:

GroupBy(Function(book) New With {.Publisher = book.Publisher.Name,
                                  book.Subject})

That's not equivalent to the C# version, because unfortunately VB uses mutable properties in anonymous types by default, and mutable properties aren't considered as part of the hash code or equality operations. You need to make both properties "Key" properties:

GroupBy(Function(book) New With {Key .Publisher = book.Publisher.Name,
                                 Key book.Subject})

Then it should work fine. You can read more about anonymous types in VB on MSDN.

Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • 1
    Yes this works great; thanks a lot! Wow, an answer by Jon Skeet himself. I feel honored. :-) – Markus Oct 26 '11 at 06:22
  • I tested my query that was failing due to this in LINQPad and I used a LINQ to Entities source. In my code I am actually materializing the query (ToList) and running the group query from that since I have to do some per-row calculations in the interim. This leads me to believe this is a problem in LINQ to Objects but not LINQ to Entities. I'm assuming this would be because in LINQ to Entities, the grouping expression is translated to TSQL whereas in LINQ to Objects, the hash code is used for group key equality. Does this sound correct? – pseudocoder Feb 27 '14 at 18:54
  • @pseudocoder: No, it's not about the hash code - it's about which properties are used to determine equality. It sounds like LINQ to Entities treats *all* properties as key properties... which sounds like a bug to me. This isn't a "problem" in LINQ to Objects - it's just a matter of how anonymous types in VB work. – Jon Skeet Feb 27 '14 at 18:57
5

While I applaud your efforts to translate the samples, we actually have all of the samples for LINQ in Action in C# and VB available for download from the Manning Site: http://www.manning.com/marguerie/. Also, we have added samples to the LinqPad samples to make it easy to try the samples and save your changes. See http://www.thinqlinq.com/Default/LINQ-In-Action-Samples-available-in-LINQPad.aspx for instructions on how to access that.

It appears that you are working on example 5.06b. Updating it slightly, our VB translation is:

Dim query = _
  From book In SampleData.Books _
  Group book.Title By book.Publisher, book.Subject Into grouping = Group _
  Select _
    Publisher = Publisher.Name, _
    Subject = Subject.Name, _
    Titles = grouping 

If you want to use the Lambda syntax, you do need to specify the Key as @johnskeet indicated:

Dim list = SampleData.Books.GroupBy(Function(book) New With { 
                          Key .Publisher = book.Publisher.Name, 
                          Key .Subject = book.Subject}). 
               Select(Function(grouping) New With {
                  .Publisher = grouping.Key.Publisher, 
                  .Subject = grouping.Key.Subject, 
                  .Books = grouping}) 
Jim Wooley
  • 9,725
  • 1
  • 22
  • 42