86 votes

Comment utiliser la fonction Distinct() de LINQ avec des champs multiples ?

J'ai les éléments suivants Classe EF dérivé d'une base de données (simplifié)

class Product
{ 
     public string ProductId;
     public string ProductName;
     public string CategoryId;
     public string CategoryName;
}

ProductId est le Clé primaire de la table.

Pour une mauvaise décision de conception prise par le concepteur de la BD (je ne peux pas la modifier), j'ai CategoryId y CategoryName dans ce tableau.

J'ai besoin d'un Liste DropDownList avec (distinct) CategoryId como Valeur y CategoryName como Texte . J'ai donc appliqué le code suivant :

product.Select(m => new {m.CategoryId, m.CategoryName}).Distinct();

ce qui logiquement devrait créer un objet anonyme avec CategoryId y CategoryName en tant que propriétés. Le site Distinct() garantit qu'il n'y a pas de paire de doublons ( CategoryId , CategoryName ).

Mais en fait, cela ne fonctionne pas. D'après ce que j'ai compris, le Distinct() ne fonctionne que lorsqu'il n'y a qu'un seul champ dans la collection, sinon il les ignore... Est-ce correct ? Existe-t-il une solution de contournement ? Merci !

UPDATE

Désolé product est :

List<Product> product = new List<Product>();

J'ai trouvé une autre façon d'obtenir le même résultat. Distinct() :

product.GroupBy(d => new {d.CategoryId, d.CategoryName}) 
       .Select(m => new {m.Key.CategoryId, m.Key.CategoryName})

85voto

Tim Schmelter Points 163781

Je suppose que vous utilisez distinct comme un appel de méthode sur une liste. Vous devez utiliser le résultat de la requête comme source de données pour votre DropDownList, par exemple en le matérialisant via ToList .

var distinctCategories = product
                        .Select(m => new {m.CategoryId, m.CategoryName})
                        .Distinct()
                        .ToList();
DropDownList1.DataSource     = distinctCategories;
DropDownList1.DataTextField  = "CategoryName";
DropDownList1.DataValueField = "CategoryId";

Une autre solution, si vous avez besoin d'objets réels au lieu d'un type anonyme avec seulement quelques propriétés, est d'utiliser GroupBy avec un type anonyme :

List<Product> distinctProductList = product
    .GroupBy(m => new {m.CategoryId, m.CategoryName})
    .Select(group => group.First())  // instead of First you can also apply your logic here what you want to take, for example an OrderBy
    .ToList();

Une troisième option consiste à utiliser MoreLinq's DistinctBy .

11voto

sehe Points 123151

La fonction Distinct() garantit qu'il n'y a pas de paire en double (CategoryId, CategoryName).

- exactement cela

Les types anonymes mettent en œuvre "par magie Equals y GetHashcode

Je suppose qu'il y a une autre erreur quelque part. Sensibilité à la casse ? Classes mutables ? Champs non comparables ?

6voto

Node.JS Points 787

Voici ma solution, elle prend en charge les keySelectors de différents types :

public static IEnumerable<TSource> DistinctBy<TSource>(this IEnumerable<TSource> source, params Func<TSource, object>[] keySelectors)
{
    // initialize the table
    var seenKeysTable = keySelectors.ToDictionary(x => x, x => new HashSet<object>());

    // loop through each element in source
    foreach (var element in source)
    {
        // initialize the flag to true
        var flag = true;

        // loop through each keySelector a
        foreach (var (keySelector, hashSet) in seenKeysTable)
        {                    
            // if all conditions are true
            flag = flag && hashSet.Add(keySelector(element));
        }

        // if no duplicate key was added to table, then yield the list element
        if (flag)
        {
            yield return element;
        }
    }
}

Pour l'utiliser :

list.DistinctBy(d => d.CategoryId, d => d.CategoryName)

5voto

Michael Stramel Points 137

Utilisez le Key Le mot-clé dans votre sélection fonctionnera, comme ci-dessous.

product.Select(m => new {Key m.CategoryId, Key m.CategoryName}).Distinct();

Je me rends compte qu'il s'agit d'un vieux sujet, mais j'ai pensé que cela pourrait aider certaines personnes. Je code généralement en VB.NET lorsque je travaille avec .NET. Key peut se traduire différemment en C#.

4voto

Sergey Berezovskiy Points 102044

Distinct renvoie des éléments distincts d'une séquence.

Si vous jetez un coup d'œil sur son implémentation avec Reflector, vous verrez qu'il crée des DistinctIterator pour votre type d'anonymat. L'itérateur distinct ajoute des éléments à Set lors de l'énumération d'une collection. Cet énumérateur saute tous les éléments qui sont déjà dans la collection. Set . Set utilise GetHashCode y Equals méthodes pour définir si un élément existe déjà dans Set .

Comment GetHashCode y Equals mis en œuvre pour le type anonyme ? Comme il est indiqué sur msdn :

Les méthodes Equals et GetHashCode sur les types anonymes sont définies en termes de des méthodes Equals et GetHashcode de leurs propriétés, deux instances du même type anonyme ne sont égales que si toutes leurs propriétés sont égales.

Ainsi, vous devez absolument avoir des objets anonymes distincts, lorsque vous itérez sur une collection distincte. Et le résultat ne dépend pas du nombre de champs que vous utilisez pour votre type anonyme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X