Verwendung von LINQ Distinct () mit mehreren Feldern

75

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

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

ProductIdist der Primärschlüssel der Tabelle.

Für eine schlechte Entwurfsentscheidung des DB-Designers (ich kann sie nicht ändern) habe ich CategoryIdund CategoryNamein dieser Tabelle.

Ich benötige eine DropDownList mit (eindeutig) CategoryIdals Wert und CategoryNameals Text . Deshalb habe ich folgenden Code angewendet:

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

logischerweise sollte ein anonymes Objekt mit CategoryIdund CategoryNameals Eigenschaften erstellt werden. Das Distinct()garantiert, dass es keine doppelten Paare gibt ( CategoryId, CategoryName).

Aber eigentlich funktioniert es nicht. Soweit ich verstanden habe, Distinct()funktionieren die Werke nur, wenn es nur ein Feld in der Sammlung gibt, sonst werden sie einfach ignoriert ... ist es richtig? Gibt es eine Problemumgehung? Vielen Dank!

AKTUALISIEREN

Entschuldigung productist:

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

Ich habe einen alternativen Weg gefunden, um das gleiche Ergebnis zu erzielen wie Distinct():

product.GroupBy(d => new {d.CategoryId, d.CategoryName}) 
       .Select(m => new {m.Key.CategoryId, m.Key.CategoryName})
CiccioMiami
quelle
"Es gibt nur ein Feld in der Sammlung" ist unsinnig. Was meinst du?
Leppie
1
@leppie Ich vermute, er meint, wenn er auf einen einzelnen Wert projiziert, nicht auf einen anonymen Typ (der mehr als ein Feld enthält).
sehe
"Für eine schlechte Entwurfsentscheidung des DB-Designers (ich kann sie nicht ändern)". Sie können die Datenbank möglicherweise nicht ändern, dies bedeutet jedoch nicht, dass Sie dies in Ihrem EF-Modell nicht beheben können. Das ist der Beaty von EF.
Steven
Wo funktioniert es nicht? Wo bist du in: classic asp.net, mvc? Was ist "Produkt" in product.Select?
Raphaël Althaus
@ leppie, sorry ich meinte "Eigentum in der Sammlung"
CiccioMiami

Antworten:

69

Ich gehe davon aus, dass Sie eine bestimmte Methode wie einen Methodenaufruf für eine Liste verwenden. Sie müssen das Ergebnis der Abfrage als Datenquelle für Ihre DropDownList verwenden, indem Sie es beispielsweise über materialisieren ToList.

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

Eine andere Möglichkeit, wenn Sie die realen Objekte anstelle des anonymen Typs mit nur wenigen Eigenschaften benötigen, ist die Verwendung GroupBymit einem anonymen Typ:

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();

Eine dritte Option ist die Verwendung von MoreLinqsDistinctBy .

Tim Schmelter
quelle
1
@CiccioMiami upvoters, stellen Sie sicher, dass Sie diese Antwort nur auf anonyme Typen anwenden. Typisierte Klassen erfordern möglicherweise einen Delegaten wie diesen .
Crokusek
Richtig, die Unterscheidung funktioniert nur mit
anonymem
1
@imdadhusen: Nicht nur bei anonymen Typen muss der Typ Equals+ überschreiben, GetHashCodeoder Sie müssen IEQualityCpomparer<TypeName>bei der Überladung von einen benutzerdefinierten Typ angeben Distinct.
Tim Schmelter
@ Tim, kann ich Where & Group By-Bedingungen zusammen verwenden? In Ihrer zweiten Abfrage oder Ihrem zweiten Code möchte ich nur die Where-Bedingung vor der Gruppe anwenden. Ist dies also möglich?
Md Aslam
11

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

- genau das

Anonyme Typen 'magisch' implementieren EqualsundGetHashcode

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

sehe sehen
quelle
Das ist was ich zwar, aber die Antwort unten sagt nein. Ich bin verwirrt.
Leppie
@Ieppie stackoverflow.com/questions/543482/…
Raphaël Althaus
4

Die Distinct- Methode gibt verschiedene Elemente aus einer Sequenz zurück.

Wenn Sie sich die Implementierung mit Reflector ansehen, werden Sie feststellen, dass sie DistinctIteratorfür Ihren anonymen Typ erstellt wird. Ein eindeutiger Iterator fügt Elemente hinzu, Setwenn über die Sammlung aufgezählt wird. Dieser Enumerator überspringt alle Elemente, die bereits vorhanden sind Set. SetVerwendungen GetHashCodeund EqualsMethoden zum Definieren, ob das Element bereits in vorhanden ist Set.

Wie GetHashCodeund Equalsfür anonymen Typ implementiert? Wie auf msdn angegeben :

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

Sie sollten also auf jeden Fall unterschiedliche anonyme Objekte haben, wenn Sie eine bestimmte Sammlung durchlaufen. Das Ergebnis hängt nicht davon ab, wie viele Felder Sie für Ihren anonymen Typ verwenden.

Sergey Berezovskiy
quelle
4

Verwenden Sie das KeySchlüsselwort in Ihrer Auswahl, wie unten beschrieben.

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

Mir ist klar, dass dies einen alten Thread aufwirft, aber ich dachte, es könnte einigen Leuten helfen. Ich codiere im Allgemeinen in VB.NET, wenn ich mit .NET arbeite, daher Keykann es sein, dass es anders in C # übersetzt wird.

Michael Stramel
quelle
3
Um in VB.Net mehrere Felder zu unterscheiden, müssen die Schlüsselwörter 'Key' für jede Eigenschaft im anonymen Typ verwendet werden. (Andernfalls wird der zum Vergleich verwendete Hashcode nicht richtig berechnet.) In C # gibt es kein Schlüsselwort "Schlüssel". Stattdessen sind alle Eigenschaften in anonymen Typen automatisch Schlüsselfelder.
Voon
4

Beantwortung der Überschrift der Frage (was die Leute hier angezogen hat) und Ignorieren, dass im Beispiel anonyme Typen verwendet wurden ....

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 /programming/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().
    /// /programming/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 besteht darin, einer Zeichenfolgendarstellung zuzuordnen, damit benutzerdefiniertes Hashing vermieden wird. Gleiche Zeichenfolgen haben bereits gleiche Hash-Codes.

Referenz:
Wickeln Sie einen Delegaten in einen IEqualityComparer

crokusek
quelle
4

Dies ist meine Lösung, sie 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)
Node.JS
quelle
1
Ein Vorteil dieses Ansatzes besteht darin, dass er sowohl für anonyme als auch für definierte Typen funktioniert.
Crokusek
Hatte Probleme mit dieser Lösung in .net 4.7.2. Vielleicht funktioniert dies nur in .net Core
drzounds
1
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;

    }
Saktiprasad Swain
quelle
0

Die Lösung für Ihr Problem sieht folgendermaßen aus:

public class Category {
  public long CategoryId { get; set; }
  public string CategoryName { get; set; }
} 

...

public class CategoryEqualityComparer : IEqualityComparer<Category>
{
   public bool Equals(Category x, Category y)
     => x.CategoryId.Equals(y.CategoryId)
          && x.CategoryName .Equals(y.CategoryName, 
 StringComparison.OrdinalIgnoreCase);

   public int GetHashCode(Mapping obj)
     => obj == null 
         ? 0
         : obj.CategoryId.GetHashCode()
           ^ obj.CategoryName.GetHashCode();
}

...

 var distinctCategories = product
     .Select(_ => 
        new Category {
           CategoryId = _.CategoryId, 
           CategoryName = _.CategoryName
        })
     .Distinct(new CategoryEqualityComparer())
     .ToList();
Paul Alexeev
quelle
-3
Employee emp1 = new Employee() { ID = 1, Name = "Narendra1", Salary = 11111, Experience = 3, Age = 30 };Employee emp2 = new Employee() { ID = 2, Name = "Narendra2", Salary = 21111, Experience = 10, Age = 38 };
Employee emp3 = new Employee() { ID = 3, Name = "Narendra3", Salary = 31111, Experience = 4, Age = 33 };
Employee emp4 = new Employee() { ID = 3, Name = "Narendra4", Salary = 41111, Experience = 7, Age = 33 };

List<Employee> lstEmployee = new List<Employee>();

lstEmployee.Add(emp1);
lstEmployee.Add(emp2);
lstEmployee.Add(emp3);
lstEmployee.Add(emp4);

var eemmppss=lstEmployee.Select(cc=>new {cc.ID,cc.Age}).Distinct();
Narendra Kumar Samal
quelle
Was ist, wenn ich jetzt nach Alter bestellen möchte?
Si8