web-dev-qa-db-fra.com

Comment utiliser LINQ Distinct () avec plusieurs champs

J'ai le suivant 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 la clé primaire de la table. 

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

J'ai besoin d'un DropDownList avec (distinct) CategoryId en tant que Valeur et CategoryName en tant que 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 et CategoryName comme propriétés. Distinct() garantit qu'il n'y a pas de doublons (CategoryId, CategoryName). 

Mais en réalité ça ne marche pas. Autant que je sache, Distinct() fonctionne uniquement lorsqu'il n'y a qu'un seul champ dans la collection, sinon il les ignore ... est-ce correct? Y a-t-il une solution de contournement? Merci!

METTRE À JOUR

Désolé product est:

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

J'ai trouvé un autre moyen d'obtenir le même résultat que Distinct():

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

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 en tant que 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";
42
Rango

Distinct () garantit l’absence de doublons (CategoryId, CategoryName).

- exactement ça

Les types anonymes "magiquement" implémentent Equals et GetHashcode

Je suppose une autre erreur quelque part. Sensibilité à la casse? Classes mutable? Des champs non comparables?

11
sehe

Distinct méthode retourne des éléments distincts d'une séquence. 

Si vous regardez son implémentation avec Reflector, vous verrez qu'il crée DistinctIterator pour votre type anonyme. Distinct iterator ajoute des éléments à Set lors de l'énumération de la collection. Cet énumérateur ignore tous les éléments déjà présents dans Set. Set utilise les méthodes GetHashCode et Equals pour définir si l'élément existe déjà dans Set

Comment GetHashCode et Equals sont-ils implémentés pour le type anonyme? Comme indiqué sur msdn :

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

Donc, vous devriez certainement avoir des objets anonymes distincts, lors de l'itération sur une collection distincte. Et le résultat ne dépend pas du nombre de champs que vous utilisez pour votre type anonyme.

4

Ceci est ma solution, il supporte 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)
3
Node.JS

Répondre au titre de la question (ce qui a attiré les gens ici) et ignorer que l'exemple utilise des types anonymes ....

Cette solution fonctionnera également pour les types non anonymes. Il ne devrait pas être nécessaire pour les types anonymes.

Classe d'assistance:

/// <summary>
/// Allow IEqualityComparer to be configured within a lambda expression.
/// From https://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer
/// </summary>
/// <typeparam name="T"></typeparam>
public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    /// <summary>
    /// Simplest constructor, provide a conversion to string for type T to use as a comparison key (GetHashCode() and Equals().
    /// https://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer, user "orip"
    /// </summary>
    /// <param name="toString"></param>
    public LambdaEqualityComparer(Func<T, string> toString)
        : this((t1, t2) => toString(t1) == toString(t2), t => toString(t).GetHashCode())
    {
    }

    /// <summary>
    /// Constructor.  Assumes T.GetHashCode() is accurate.
    /// </summary>
    /// <param name="comparer"></param>
    public LambdaEqualityComparer(Func<T, T, bool> comparer)
        : this(comparer, t => t.GetHashCode())
    {
    }

    /// <summary>
    /// Constructor, provide a equality comparer and a hash.
    /// </summary>
    /// <param name="comparer"></param>
    /// <param name="hash"></param>
    public LambdaEqualityComparer(Func<T, T, bool> comparer, Func<T, int> hash)
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hash(obj);
    }    
}

Utilisation la plus simple:

List<Product> products = duplicatedProducts.Distinct(
    new LambdaEqualityComparer<Product>(p =>
        String.Format("{0}{1}{2}{3}",
            p.ProductId,
            p.ProductName,
            p.CategoryId,
            p.CategoryName))
        ).ToList();

L'utilisation la plus simple (mais pas aussi efficace) consiste à mapper une représentation sous forme de chaîne afin d'éviter le hachage personnalisé. Les chaînes égales ont déjà des codes de hachage égaux.

Référence:
Envelopper un délégué dans un IEqualityComparer

0
crokusek
public List<ItemCustom2> GetBrandListByCat(int id)
    {

        var OBJ = (from a in db.Items
                   join b in db.Brands on a.BrandId equals b.Id into abc1
                   where (a.ItemCategoryId == id)
                   from b in abc1.DefaultIfEmpty()
                   select new
                   {
                       ItemCategoryId = a.ItemCategoryId,
                       Brand_Name = b.Name,
                       Brand_Id = b.Id,
                       Brand_Pic = b.Pic,

                   }).Distinct();


        List<ItemCustom2> ob = new List<ItemCustom2>();
        foreach (var item in OBJ)
        {
            ItemCustom2 abc = new ItemCustom2();
            abc.CategoryId = item.ItemCategoryId;
            abc.BrandId = item.Brand_Id;
            abc.BrandName = item.Brand_Name;
            abc.BrandPic = item.Brand_Pic;
            ob.Add(abc);
        }
        return ob;

    }
0
saktiprasad swain