Agrupamento por várias colunas em LINQ: exemplos e melhores práticas

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Como agrupar dados por dois ou mais campos usando LINQ em C#?

\n\n

Olá, amigo! Se você já escreveu consultas para bancos de dados em SQL, certamente se lembra da construção GROUP BY com várias colunas. Ela permite dividir os dados em grupos lógicos — por exemplo, agrupar vendas primeiro por cidade e, dentro dela, por categoria de produto. Em C#, para o mesmo propósito, usamos GroupBy do LINQ. Mas aí está o problema: o GroupBy padrão aceita apenas uma chave. O que fazer se precisar agrupar por várias colunas?

\n\n

Não se preocupe! A solução é elegante e simples — criamos um tipo anônimo que combina os campos necessários em uma chave composta. Neste artigo, mostrarei não apenas a sintaxe básica, mas também exemplos reais com código que você poderá aplicar imediatamente em seus projetos. Vamos lá!

\n\n

Problema: uma chave é muito pouco

\n\n

Imagine que você tem uma lista de alunos:

\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 = "Matemática", Grade = 85 },\n    new Student { Name = "Boris", Course = "Matemática", Grade = 90 },\n    new Student { Name = "Anna", Course = "Física", Grade = 78 },\n    new Student { Name = "Boris", Course = "Física", Grade = 82 },\n    new Student { Name = "Anna", Course = "Matemática", Grade = 95 }\n};
\n\n

Você quer saber a nota média de cada aluno em cada curso. Se usar GroupBy apenas pelo nome, os dados da Anna em Matemática e Física se misturarão. Se usar apenas pelo curso, perderá a referência ao aluno. É necessário um agrupamento por duas colunas: Name + Course.

\n\n

Solução: tipo anônimo como chave composta

\n\n

Em C#, podemos criar um objeto temporário com os campos necessários diretamente no local:

\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} | Média: {item.AverageGrade:F2}");\n}
\n\n

Saída:
\nAnna | Matemática | Média: 90.00
\nBoris | Matemática | Média: 90.00
\nAnna | Física | Média: 78.00
\nBoris | Física | Média: 82.00

\n\n

Observe: new { s.Name, s.Course } é o tipo anônimo. O LINQ compara automaticamente os objetos por todas as suas propriedades (igualdade de valor).

\n\n

Trabalhando com IGrouping e coleções aninhadas

\n\n

Às vezes, não é necessário agregar os dados, mas sim manter todos os elementos originais dentro do grupo. Para isso, usamos a interface IGrouping<TKey, TElement>. Cada grupo contém uma chave e uma coleção de elementos:

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

Essa abordagem é ideal quando você precisa, por exemplo, gerar um relatório detalhado.

\n\n

Sintaxe query vs fluent — qual escolher?

\n\n

O LINQ suporta dois estilos de escrita de consultas. Vamos ver ambos:

\n\n

1. Fluent (sintaxe de método)

\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 (sintaxe de expressões semelhantes a SQL)

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

Blogs

Book Recommendations