Utilisation de LINQ pour regrouper, fusionner et supprimer les doublons dans des collections

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.

Étiquettes: LINQ C# GroupBy Aggregation object-anonymous

Publié le 27 septembre à 20h04