Explain `GroupBy()` and `Join()` operations in LINQ.
4 minintermediate.NETLINQgroupingjoin
Quick Answer
`GroupBy()` organizes a sequence into groups keyed by a selector, returning `IGrouping<TKey,TElement>` groups you can aggregate (count, sum, etc.). `Join()` correlates two sequences on matching keys (an inner join), producing combined results; `GroupJoin()` produces grouped (left-join-like) results. Together they cover SQL-style grouping and joining over in-memory or database data.
Detailed Answer
GroupBy()
- Groups elements by a specified key
- Returns
IEnumerable<IGrouping<TKey, TElement>> - Each group has a Key property and contains elements
var students = new[]
{
new { Name = "Alice", Grade = "A", Age = 20 },
new { Name = "Bob", Grade = "B", Age = 22 },
new { Name = "Charlie", Grade = "A", Age = 21 },
new { Name = "Diana", Grade = "B", Age = 20 }
};
// Group by Grade
var byGrade = students.GroupBy(s => s.Grade);
foreach (var group in byGrade)
{
Console.WriteLine($"Grade {group.Key}:");
foreach (var student in group)
{
Console.WriteLine($" {student.Name}");
}
}
// Output:
// Grade A:
// Alice
// Charlie
// Grade B:
// Bob
// Diana
// Group with projection
var gradeStats = students.GroupBy(s => s.Grade)
.Select(g => new
{
Grade = g.Key,
Count = g.Count(),
AverageAge = g.Average(s => s.Age),
Students = g.Select(s => s.Name).ToList()
});
// Group by multiple keys
var byGradeAndAge = students.GroupBy(s => new { s.Grade, s.Age });
Join()
- Performs inner join (like SQL JOIN)
- Combines elements from two sequences based on matching keys
- Returns only matching pairs
var students = new[]
{
new { Id = 1, Name = "Alice" },
new { Id = 2, Name = "Bob" },
new { Id = 3, Name = "Charlie" }
};
var grades = new[]
{
new { StudentId = 1, Course = "Math", Grade = "A" },
new { StudentId = 2, Course = "Math", Grade = "B" },
new { StudentId = 1, Course = "Science", Grade = "A" },
new { StudentId = 4, Course = "Math", Grade = "C" } // No matching student
};
// Inner Join
var studentGrades = students.Join(
grades, // Second collection
student => student.Id, // Key from first collection
grade => grade.StudentId, // Key from second collection
(student, grade) => new // Result selector
{
student.Name,
grade.Course,
grade.Grade
}
);
// Result: Only matching pairs (StudentId 4 excluded)
// { Name = "Alice", Course = "Math", Grade = "A" }
// { Name = "Alice", Course = "Science", Grade = "A" }
// { Name = "Bob", Course = "Math", Grade = "B" }
// Query syntax
var queryResult = from student in students
join grade in grades on student.Id equals grade.StudentId
select new { student.Name, grade.Course, grade.Grade };
// GroupJoin - like LEFT JOIN
var studentAllGrades = students.GroupJoin(
grades,
student => student.Id,
grade => grade.StudentId,
(student, studentGrades) => new
{
student.Name,
Grades = studentGrades.ToList()
}
);
// Charlie will appear with empty Grades list
Common Patterns:
// Multiple joins
var result = from student in students
join grade in grades on student.Id equals grade.StudentId
join teacher in teachers on grade.TeacherId equals teacher.Id
select new { student.Name, grade.Course, teacher.Name };
// Join with composite keys
var result = students.Join(
enrollments,
s => new { s.Id, s.Year },
e => new { e.StudentId, e.Year },
(s, e) => new { s.Name, e.Course }
);
// Left outer join pattern
var leftJoin = from student in students
join grade in grades on student.Id equals grade.StudentId
into studentGrades
from grade in studentGrades.DefaultIfEmpty()
select new
{
student.Name,
Course = grade?.Course ?? "No courses"
};