Written by 19:13 SQL Server

The LINQ GroupBy Method

We are continuing our series dedicated to LINQ technologies with a more detailed look at some methods available under the LINQ umbrella. The focus of the current article is the GroupBy method.

By definition, the GroupBy method groups the elements of a sequence by the specified key selector function and returns the result value from each group and its key.

Based on the official Microsoft documentation, the GroupBy method has eight overloads. You will find their definitions in the documents. In this article, we will focus on examples to bring closer and explain different usages of the GroupBy method.

Let’s See it in Action

As usual, we are back in our university, and for all of our examples, we will use the Student class defined here.

public class Student
        {
            public int Id { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int BirthYear { get; set; }
            public enum GenderType { Female, Male };
            public GenderType Gender { get; set; }
            public string Interests { get; set; }
        }

Currently, we have 100 students. First, let’s see how many students of each gender we have. To do that we will use the following query:

var groupByGender = students.GroupBy(m => m.Gender);

The type of the result is IGrouping<TKey,TElement>. In this example, it means

System.Linq.GroupedEnumerable`3[LinqDemoDataModel.Student,LinqDemoDataModel.Student+GenderType,LinqDemoDataModel.Student]

We can iterate through the result to get concrete numbers.

foreach (var group in groupByGender)
                Console.WriteLine("{0}: {1}", group.Key, group.Count());

Male: 64
Female: 36

For a bit more advanced example, let’s see how we can group by multiple columns. We will group our students by gender and birth year.

var groupByGenderAndBirthYear = students.GroupBy(m => new { m.Gender, m.BirthYear });
foreach (var group in groupByGenderAndBirthYear)
      Console.WriteLine("{0} {1}: {2}", group.Key.Gender, group.Key.BirthYear, group.Count());

As you can see, the selector key is defined as a new AnonymousType object. These properties are those we wanted to group by. It might be a bit clearer if we change our code to this:

var groupByGenderAndBirthYear = students.GroupBy(m => new { Column1 = m.Gender, Column2 = m.BirthYear });
foreach (var group in groupByGenderAndBirthYear)
Console.WriteLine("{0} {1}: {2}", group.Key.Column1, group.Key.Column2, group.Count());

More Advanced Examples

In the following table, we will show some examples of different GroupBy usages, both in Query and Method syntax.

Query DescriptionMethod DefinitionQuery SyntaxMethod Syntax
Get number of students grouped by genderpublic static System.Collections.Generic.IEnumerable<TResult> GroupBy<TSource,TKey,TElement,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector, Func<TSource,TElement> elementSelector, Func<TKey,System.Collections.Generic.IEnumerable<TElement>,TResult> resultSelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer);students.GroupBy(m => m.Gender, m => new { m.FirstName, m.LastName }, (key, results) => new {Key = key, Count = results.Count() });  from m in students group new { m.FirstName, m.LastName } by m.Gender into g select new { Key = g.Key, Count = g.Count() };
Get the accumulative number age of all students grouped by genderpublic static System.Collections.Generic.IEnumerable<TResult> GroupBy<TSource,TKey,TElement,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector, Func<TSource,TElement> elementSelector, Func<TKey,System.Collections.Generic.IEnumerable<TElement>,TResult> resultSelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer);students.GroupBy(m => m.Gender, m => DateTime.Now.Year – m.BirthYear, (key, results) => new { Key = key, AccAge = results.Sum()});from m in students group DateTime.Now.Year – m.BirthYear by m.Gender into results select new { Key = results.Key, AccAge = results.Sum() };
Get the average age of students grouped by genderpublic static System.Collections.Generic.IEnumerable<System.Linq.IGrouping<TKey,TElement>> GroupBy<TSource,TKey,TElement> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector, Func<TSource,TElement> elementSelector);students.GroupBy(m => m.Gender, m => m.BirthYear, (key, results) => new { Key = key, AverageAge = (int)results.Average(x => DateTime.Now.Year – x) });from m in students group m.BirthYear by m.Gender into results select new { Key = results.Key, AverageAge = (int)results.Average(x => DateTime.Now.Year – x) };

IEqualityComparer Usage

One GroupBy overload property which is not covered or used as much as the other ones is the IEqualityComparer property.

Let’s change the last example a bit: instead of counting the number of interests when defining the selector key, we’ll use custom IEqualityComparer.

First, we need to define the class StudentInterestsComparer as:

class StudentInterestsComparer : IEqualityComparer<string>
        {
            public bool Equals(string i1, string i2)
            {
                return i1.Split(' ').Count() == i2.Split(' ').Count();
            }

            public int GetHashCode(string i)
            {
                return i.Split(' ').Count().GetHashCode();
            }
        }

And then we can use it in our query. Instead of

var result = students.GroupBy(m => m.Interests.Split(' ').Count(), (key, results) => new { Key = key, Count = results.Count() });

we will use the following code to group students and display results

var result = students.GroupBy(m => m.Interests, m => m.Interests, (key, results) => new { Key = key, Count = results.Count() }, new StudentInterestsComparer());

Here is the code for both examples:

Console.WriteLine("Inline interests count results:");
            var result1 = students.GroupBy(m => m.Interests.Split(' ').Count(), (key, results) => new { Key = key, Count = results.Count() });
            foreach (var group in result1)
                Console.WriteLine("Interests count: {0}, Students count: {1} ", group.Key, group.Count);
            Console.WriteLine("");
            Console.WriteLine("IEqualityComparer usage results:");
            var result = students.GroupBy(m => m.Interests, m => m.Interests, (key, results) => new { Key = key, Count = results.Count() }, new StudentInterestsComparer());
            foreach (var group in result)
                Console.WriteLine("Interests: {0}, Students count: {1}", group.Key, group.Count);

The below image shows the comparison of results of both examples:

We can notice that the Student counts match in both cases. We can also see that if we use the IEqualityComparer, the selector key is the first instance of the Interests value. All others that match the number of interests, as is defined in StudentInterestsComparer, will go under that key.

Conclusion

Thus, we have seen several examples of the GroupBy method. The basic usage is pretty straightforward. More advanced cases can become a bit difficult to understand, but this provides us with more options to construct more optimized queries.

I hope you enjoyed the article and will continue to follow our LINQ-related articles.

Last modified: March 18, 2022
Close