Most of the times I’m doing my code samples with C# and XAML. Of course, some samples are in C++/CLI and F#. If a customer demands I’m also doing Visual Basic. This is when it comes to translate C# to Visual Basic.
In this blog post I’m showing LINQ queries with grouping.
First, let’s start with C#. As usual I’ve a list of Formula 1 world champions that are described by a simple Racer class.
public class Racer { public string FirstName { get; set; } public string LastName { get; set; } public int Wins { get; set; } public string Country { get; set; } public int Starts { get; set; } public int PolePositions { get; set; } //...
The collection I’m working on are all Formula 1 champions since 1950 that is returned from Formula1.GetChampions().
To group the list of racers based on the country to get the number of champions of each country. This is a simple group by statement as shown.
var q = from r in Formula1.GetChampions() group r by r.Country into g orderby g.Count() descending, g.Key select new { Country = g.Key, Count = g.Count() };
The group by clause gets translated from the compiler to the GroupBy method. GroupBy gets a key-selector as argument of type Func<T, TKey>. In the sample this is Func<Racer, string> as grouping the collection by the country which is of type string. GroupBy returns IEnumerable<IGrouping<TKey, T>> what is IEnumerable<IGrouping<string, Racer>> with the sample.
The result from the grouping is ordered based on the number of elements within a group by using the Count extension method. If the count is the same, it is ordered by the key which is the country.
The result that is returned is an anonymous type with the properties Country and Count.
As the sample is now, the Count method is invoked two times. This can be changed by creating a variable within the LINQ query using the let clause.
var q = from r in Formula1.GetChampions() group r by r.Country into g let count = g.Count() orderby count descending, g.Key select new { Country = g.Key, Count = count };
Adding the racers to the result of each country is done in the next version by adding a Racers property to the anonymous type that is returned from the query. The Racers property is filled by a LINQ query that accesses the group.
var q = from r in Formula1.GetChampions() group r by r.Country into g let count = g.Count() orderby count descending, g.Key select new { Country = g.Key, Count = count, Racers = from r1 in g select r1.FirstName + " " + r1.LastName };
To reduce the result of the query to the first five groups the extension method Take is used to just return the first five.
var q = (from r in Formula1.GetChampions() group r by r.Country into g let count = g.Count() orderby count descending, g.Key select new { Country = g.Key, Count = count, Racers = from r1 in g select r1.FirstName + " " + r1.LastName }).Take(5);
Doing the same query by using extension methods instead of the LINQ Query syntax looks as shown next. The method that follows the GroupBy method can use the IEnumerable<IGrouping<string, Racer>> type that is returned.
The let clause from the LINQ query is converted to a Select method. The let clause was used to define a variable that could be used in the following LINQ clauses. The same is true by returning an anonymous type that contains the Count property from the Select method. For using the group as well with the methods that follow the Select method, the anonymous type contains the group as well.
var q = Formula1.GetChampions() .GroupBy(r => r.Country) .Select(g => new { Count = g.Count(), Group = g }) .OrderByDescending(x => x.Count) .ThenBy(x => x.Group.Key) .Select(x => new { Country = x.Group.Key, Count = x.Count, Racers = x.Group.Select(r => r.FirstName + " " + r.LastName) }) .Take(5);
Doing the same query with Visual Basic, the query looks very similar to the C# LINQ query. What’s different is that with Visual Basic Take is also defined with the LINQ query clauses.
Dim q = From r In Formula1.GetChampions() Group By country = r.Country Into Group Let count = Group.Count() Order By count Descending, country Take 5 Select Count = count, Country = country, Racers = From r In Group Order By r.LastName Select r.FirstName + " " + r.LastName
What’s also different with Visual Basic is that it’s not necessary to define a variable with the Group By clause to add the group to it, instead Group can be used. If multiple groups are used within one query, a variable can be assigned with into:
Dim q = From r In Formula1.GetChampions() Group By country = r.Country Into g = Group Let count = g.Count() Order By count Descending, country Take 5 Select Count = count, Country = country, Racers = From r In g Order By r.LastName Select r.FirstName + " " + r.LastName
If you are curious about the result of the query, here it is:
10 UK Jenson Button Jim Clark Lewis Hamilton Mike Hawthorn Graham Hill Damon Hill James Hunt Nigel Mansell Jackie Stewart John Surtees 3 Brazil Emerson Fittipaldi Nelson Piquet Ayrton Senna 3 Finland Mika Hakkinen Kimi Räikkönen Keke Rosberg 2 Australia Jack Brabham Alan Jones 2 Austria Niki Lauda Jochen Rindt
More about LINQ with C# in my book Professional C# 4 with .NET 4, and with the language of your choice in my LINQ programming workshop. There’s also more about LINQ in the upcoming BASTA! On Tour in Munich.
Christian
Very interesting you post.
helpful!!
Thanks!!
Posted by: Fernando Ferreira | 06/21/2012 at 11:00 PM