Comment regrouper des données par deux champs ou plus avec LINQ en C# ?
\n\nBonjour, ami ! Si tu as déjà écrit des requêtes vers des bases de données en SQL, tu te souviens sûrement de la construction GROUP BY avec plusieurs colonnes. Elle permet de diviser les données en groupes logiques — par exemple, regrouper les ventes d'abord par ville, puis à l'intérieur par catégorie de produit. En C#, on utilise GroupBy de LINQ dans le même but. Mais voilà le problème : le GroupBy standard n'accepte qu'une seule clé. Comment faire si l'on doit regrouper par plusieurs colonnes ?
Ne t'inquiète pas ! La solution est élégante et simple — nous créons un type anonyme qui combine les champs nécessaires en une seule clé composite. Dans cet article, je vais te montrer non seulement la syntaxe de base, mais aussi des exemples réels avec du code que tu pourras appliquer immédiatement dans tes projets. C'est parti !
\n\nProblème : une seule clé, c'est trop peu
\n\nImagine que tu as une liste d'étudiants :
\n\npublic 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 = "Mathématiques", Grade = 85 },\n new Student { Name = "Boris", Course = "Mathématiques", Grade = 90 },\n new Student { Name = "Anna", Course = "Physique", Grade = 78 },\n new Student { Name = "Boris", Course = "Physique", Grade = 82 },\n new Student { Name = "Anna", Course = "Mathématiques", Grade = 95 }\n};\n\nTu veux connaître la note moyenne de chaque étudiant pour chaque cours. Si tu utilises GroupBy uniquement par nom, les données d'Anna en mathématiques et en physique seront mélangées. Si tu regroupes uniquement par cours, le lien avec l'étudiant est perdu. Il te faut un regroupement par deux colonnes : Name + Course.
Solution : le type anonyme comme clé composite
\n\nEn C#, nous pouvons créer un objet temporaire avec les champs nécessaires directement sur place :
\n\nvar 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} | Moyenne : {item.AverageGrade:F2}");\n}\n\nRésultat :
\nAnna | Mathématiques | Moyenne : 90.00
\nBoris | Mathématiques | Moyenne : 90.00
\nAnna | Physique | Moyenne : 78.00
\nBoris | Physique | Moyenne : 82.00
Remarque : new { s.Name, s.Course } est le type anonyme. LINQ compare automatiquement les objets sur toutes leurs propriétés (égalité de valeur).
Travailler avec IGrouping et les collections imbriquées
\n\nParfois, il ne s'agit pas d'agréger les données, mais de conserver tous les éléments d'origine à l'intérieur du groupe. Pour cela, on utilise l'interface IGrouping<TKey, TElement>. Chaque groupe contient une clé et une collection d'éléments :
var groups = students.GroupBy(s => new { s.Name, s.Course });\n\nforeach (var group in groups)\n{\n Console.WriteLine($"Étudiant : {group.Key.Name}, Cours : {group.Key.Course}");\n foreach (var student in group)\n {\n Console.WriteLine($" Note : {student.Grade}");\n }\n}\n\nCette approche est idéale lorsque tu as besoin, par exemple, de générer un rapport détaillé.
\n\nSyntaxe query vs fluent — que choisir ?
\n\nLINQ prend en charge deux styles d'écriture de requêtes. Voyons les deux :
\n\n1. Fluent (syntaxe de méthode)
\nvar result = students\n .GroupBy(s => new { s.Name, s.Course })\n .Select(g => new { g.Key.Name, g.Key.Course, Count = g.Count() });\n\n2. Query (syntaxe d'expressions de type SQL)
\nvar result = from student in students\n group student by new { student.Name, stud