Considérons une classe représentant un produit avec plusieurs propriétés :
public class Produit
{
public string Reference { get; set; }
public decimal Quantite { get; set; }
public string Description { get; set; }
public string Commentaire { get; set; }
}
Voici un exemple de liste contenant des produits, certains partageant la même référence :
var produits = new List<Produit>()
{
new Produit { Reference = "REF01", Quantite = 3, Description = "test1", Commentaire = "xx" },
new Produit { Reference = "REF02", Quantite = 1, Description = "test2", Commentaire = "xx" },
new Produit { Reference = "REF01", Quantite = 2, Description = "test3", Commentaire = "xx" },
new Produit { Reference = "REF03", Quantite = 4, Description = "test4", Commentaire = "xx" },
new Produit { Reference = "REF02", Quantite = 2, Description = "test5", Commentaire = "xx" }
};
Pour regrouper par référence, cumuler les quantités et concaténer les descriptions, on utilise GroupBy combiné à Select :
var resultat = produits
.GroupBy(p => p.Reference)
.Select(g => new Produit
{
Reference = g.Key,
Quantite = g.Sum(x => x.Quantite),
Description = string.Join(",", g.Select(x => x.Description).Distinct()),
Commentaire = g.First().Commentaire
})
.ToList();
resultat.ForEach(p =>
Console.WriteLine($"{p.Reference} {p.Quantite} {p.Description} {p.Commentaire}");
);
Sortie attendue :
REF01 5 test1,test3 xx
REF02 3 test2,test5 xx
REF03 4 test4 xx
En cas de nombre élevé de propriétés, il devient fastidieux de spécifier manuellement chaque champ comme g.First().Champ. Une alternative consiste à créer une méthode d'extension permettant de copier automatiquement les valeurs du premier élément du groupe :
public static class GroupingExtensions
{
public static TOutput ProjectFromGroup<TKey, TSource, TOutput>(
this IGrouping<TKey, TSource> group,
Func<TSource, TOutput> selector)
{
return selector(group.First());
}
}
Ainsi, l’expression devient plus concise :
var resultat = produits
.GroupBy(p => p.Reference)
.Select(g => new Produit
{
Reference = g.Key,
Quantite = g.Sum(x => x.Quantite),
Description = string.Join(",", g.Select(x => x.Description).Distinct()),
Commentaire = g.ProjectFromGroup(p => p.Commentaire)
});
2. Regroupement par plusieurs critères avec LINQ
Il est possible de grouper par plusieurs champs simultanément en utilisant un objet anonyme ou une structure personnalisée :
var employes = new List<Employe>()
{
new Employe { Age = 30, Sexe = "M" },
new Employe { Age = 30, Sexe = "F" },
new Employe { Age = 35, Sexe = "M" },
new Employe { Age = 30, Sexe = "M" }
};
Regroupement par âge et sexe :
// Version avec expression lambda
var resultats = employes
.GroupBy(e => new { e.Age, e.Sexe })
.Select(g => new
{
Clé = g.Key,
Nombre = g.Count()
});
// Version avec syntaxe query
var resultats2 = from emp in employes
group emp by new { emp.Age, emp.Sexe } into g
select new
{
Clé = g.Key,
Nombre = g.Count()
};
foreach (var item in resultats)
{
Console.WriteLine($"Âge: {item.Clé.Age}, Sexe: {item.Clé.Sexe} → {item.Nombre} personnes");
}
Cette approche est particulièrement utile lorsqu’on souhaite analyser des données selon plusieurs dimensions sans avoir à créer des classes intermédiaires.