it-swarm.com.de

Verwendung von LINQ Distinct () mit mehreren Feldern

Ich habe die folgende EF Klasse abgeleitet von einer Datenbank (vereinfacht)

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

ProductId ist der Primärschlüssel der Tabelle. 

Für eine schlechte Entwurfsentscheidung des DB-Designers (ich kann ihn nicht ändern) habe ich CategoryId und CategoryName in dieser Tabelle.

Ich brauche eine DropDownList mit (eindeutigen) CategoryId als Wert und CategoryName als Text . Deshalb habe ich folgenden Code angewendet:

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

welches logisch ein anonymes Objekt mit CategoryId und CategoryName als Eigenschaften erstellen soll. Die Distinct() garantiert, dass kein Duplikatpaar vorhanden ist (CategoryId, CategoryName). 

Aber eigentlich geht es nicht. Soweit ich verstanden habe, funktioniert Distinct() nur, wenn nur ein Feld in der Sammlung vorhanden ist. Andernfalls werden sie einfach ignoriert. Gibt es eine Problemumgehung? Vielen Dank!

UPDATE

Entschuldigung, product ist:

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

Ich habe einen alternativen Weg gefunden, um dasselbe Ergebnis wie Distinct() zu erhalten:

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

Ich gehe davon aus, dass Sie verschiedene Methoden wie einen Methodenaufruf in einer Liste verwenden. Sie müssen das Ergebnis der Abfrage als Datenquelle für Ihre DropDownList verwenden, z. B. durch Materialisierung über ToList.

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

Das Distinct () garantiert, dass kein Duplikatpaar (CategoryId, CategoryName) vorhanden ist.

- genau das

Anonyme Typen implementieren 'magisch' Equals und GetHashcode

Ich gehe irgendwo von einem anderen Fehler aus. Groß-/Kleinschreibung Veränderliche Klassen? Nicht vergleichbare Felder?

11
sehe

Distinct Methode gibt unterschiedliche Elemente aus einer Sequenz zurück. 

Wenn Sie sich die Implementierung mit Reflector ansehen, werden Sie feststellen, dass DistinctIterator für Ihren anonymen Typ erstellt wird. Ein eindeutiger Iterator fügt bei der Auflistung über die Auflistung Elemente zu Set hinzu. Dieser Enumerator überspringt alle Elemente, die bereits in Set enthalten sind. Set verwendet GetHashCode und Equals, um zu definieren, ob das Element bereits in Set vorhanden ist. 

Wie wurden GetHashCode und Equals für anonyme Typen implementiert? Wie heißt es auf msdn :

Equals- und GetHashCode-Methoden für anonyme Typen werden in Begriffen definiert der Equals- und GetHashcode-Methoden der Eigenschaften zwei Instanzen desselben anonymen Typs sind nur dann gleich, wenn alle ihre Eigenschaften .__ sind. gleich.

Daher sollten Sie definitiv unterschiedliche anonyme Objekte haben, wenn Sie verschiedene Sammlungen durchlaufen. Das Ergebnis hängt nicht davon ab, wie viele Felder Sie für Ihren anonymen Typ verwenden.

4

Verwenden Sie in Ihrer Auswahl das Schlüsselwort Key.

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

Mir ist klar, dass dies ein alter Faden ist, aber ich dachte, es könnte einigen Menschen helfen. Im Allgemeinen codiere ich in VB.NET, wenn ich mit .NET arbeite, sodass Key möglicherweise anders in C # übersetzt wird.

4
Michael Stramel

Dies ist meine Lösung, es unterstützt KeySelectors verschiedener Typen:

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;
        }
    }
}

Um es zu benutzen:

list.DistinctBy(d => d.CategoryId, d => d.CategoryName)
3
Node.JS

Beantworten Sie die Überschrift der Frage (was hat die Leute hier angezogen) und ignorieren Sie, dass das Beispiel anonyme Typen verwendet ....

Diese Lösung funktioniert auch für nicht anonyme Typen. Es sollte nicht für anonyme Typen benötigt werden.

Hilfsklasse:

/// <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);
    }    
}

Einfachste Verwendung:

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

Die einfachste (aber nicht so effiziente) Verwendung ist das Zuordnen zu einer Zeichenfolgendarstellung, sodass benutzerdefiniertes Hashing vermieden wird. Gleiche Zeichenketten haben bereits gleiche Hash-Codes.

Referenz:
Einen Delegaten in einen IEqualityComparer einschließen

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