Grouping by Multiple Columns in LINQ: Examples and Best Practices

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to group data by two or more fields using LINQ in C#?

\n\n

Hello, friend! If you've ever written database queries in SQL, you probably remember the GROUP BY construct with multiple columns. It allows you to split data into logical buckets — for example, grouping sales first by city, and then within that by product category. In C#, the same purpose is served by GroupBy from LINQ. But here's the catch: the standard GroupBy accepts only one key. What if you need to group by multiple columns?

\n\n

Don't worry! The solution is elegant and simple — we create an anonymous type that combines the required fields into a single composite key. In this article, I'll show you not only the basic syntax but also real code examples that you can immediately apply in your projects. Let's go!

\n\n

The Problem: One Key Is Too Little

\n\n

Imagine you have a list of students:

\n\n
public class Student\n{\n    public string Name { get; set; }\n    public string Course { get; set; }\n    public int Grade { get; set; }\n}\n\nList<Student> students = new List<Student>\n{\n    new Student { Name = "Anna", Course = "Mathematics", Grade = 85 },\n    new Student { Name = "Boris", Course = "Mathematics", Grade = 90 },\n    new Student { Name = "Anna", Course = "Physics", Grade = 78 },\n    new Student { Name = "Boris", Course = "Physics", Grade = 82 },\n    new Student { Name = "Anna", Course = "Mathematics", Grade = 95 }\n};
\n\n

You want to find the average grade of each student for each course. If you use GroupBy only by name, Anna's data for Mathematics and Physics will be mixed. If only by course, the link to the student is lost. You need grouping by two columns: Name + Course.

\n\n

The Solution: Anonymous Type as a Composite Key

\n\n

In C#, we can create a temporary object with the required fields right on the spot:

\n\n
var grouped = students\n    .GroupBy(s => new { s.Name, s.Course })\n    .Select(g => new\n    {\n        g.Key.Name,\n        g.Key.Course,\n        AverageGrade = g.Average(st => st.Grade)\n    });\n\nforeach (var item in grouped)\n{\n    Console.WriteLine($"{item.Name} | {item.Course} | Average grade: {item.AverageGrade:F2}");\n}
\n\n

Output:
\nAnna | Mathematics | Average grade: 90.00
\nBoris | Mathematics | Average grade: 90.00
\nAnna | Physics | Average grade: 78.00
\nBoris | Physics | Average grade: 82.00

\n\n

Notice: new { s.Name, s.Course } — this is the anonymous type. LINQ automatically compares objects by all their properties (value equality).

\n\n

Working with IGrouping and Nested Collections

\n\n

Sometimes you don't need to aggregate data, but rather preserve all original elements within the group. For this, the IGrouping<TKey, TElement> interface is used. Each group contains a key and a collection of elements:

\n\n
var groups = students.GroupBy(s => new { s.Name, s.Course });\n\nforeach (var group in groups)\n{\n    Console.WriteLine($"Student: {group.Key.Name}, Course: {group.Key.Course}");\n    foreach (var student in group)\n    {\n        Console.WriteLine($"  Grade: {student.Grade}");\n    }\n}
\n\n

This approach is ideal when you need, for example, to output a detailed report.

\n\n

Query Syntax vs Fluent — Which to Choose?

\n\n

LINQ supports two styles of writing queries. Let's look at both:

\n\n

1. Fluent (method syntax)

\n
var result = students\n    .GroupBy(s => new { s.Name, s.Course })\n    .Select(g => new { g.Key.Name, g.Key.Course, Count = g.Count() });
\n\n

2. Query (SQL-like expression syntax)

\n
var result = from student in students\n             group student by new { student.Name, stud                

Blogs

Book Recommendations