简体   繁体   English

如何在多个字段中使用 LINQ Distinct()

[英]How to use LINQ Distinct() with multiple fields

I have the following EF class derived from a database (simplified)我有以下从数据库派生的EF 类(简化)

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

ProductId is the Primary Key of the table. ProductId是表的主键

For a bad design decision made by the DB designer (I cannot modify it), I have CategoryId and CategoryName in this table.对于 DB 设计者做出的错误设计决定(我无法修改它),我在此表中有CategoryIdCategoryName

I need a DropDownList with (distinct) CategoryId as Value and CategoryName as Text .我需要一个DropDownList与(不同的) CategoryId作为ValueCategoryName作为Text Therefore I applied the following code:因此我应用了以下代码:

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

which logically it should create an anonymous object with CategoryId and CategoryName as properties.从逻辑上讲,它应该创建一个具有CategoryIdCategoryName作为属性的匿名对象。 The Distinct() guarantees that there are no duplicates pair ( CategoryId , CategoryName ). Distinct()保证没有重复对( CategoryIdCategoryName )。

But actually it does not work.但实际上它不起作用。 As far as I understood the Distinct() works just when there is just one field in the collection otherwise it just ignores them...is it correct?据我了解, Distinct()仅在集合中只有一个字段时才起作用,否则它只会忽略它们......是否正确? Is there any workaround?有什么解决方法吗? Thanks!谢谢!

UPDATE更新

Sorry product is:对不起product是:

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

I found an alternative way to get the same result as Distinct() :我找到了一种替代方法来获得与Distinct()相同的结果:

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

I assume that you use distinct like a method call on a list.我假设您使用 distinct 就像对列表的方法调用一样。 You need to use the result of the query as datasource for your DropDownList, for example by materializing it via ToList .您需要将查询结果用作 DropDownList 的数据源,例如通过ToList

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

Another way if you need the real objects instead of the anonymous type with only few properties is to use GroupBy with an anonymous type:如果您需要真实对象而不是只有少数属性的匿名类型,另一种方法是使用具有匿名类型的GroupBy

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

A third option is to use MoreLinq's DistinctBy .第三种选择是使用MoreLinq 的DistinctBy

The Distinct() guarantees that there are no duplicates pair (CategoryId, CategoryName). Distinct() 保证没有重复对(CategoryId、CategoryName)。

- exactly that - 正是这样

Anonymous types 'magically' implement Equals and GetHashcode匿名类型“神奇地”实现了EqualsGetHashcode

I assume another error somewhere.我假设某处有另一个错误。 Case sensitivity?区分大小写? Mutable classes?可变类? Non-comparable fields?不可比较的领域?

Use the Key keyword in your select will work, like below.在您的选择中使用Key关键字将起作用,如下所示。

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

I realize this is bringing up an old thread but figured it might help some people.我意识到这是提出了一个旧线程,但认为它可能对某些人有所帮助。 I generally code in VB.NET when working with .NET so Key may translate differently into C#.在使用 .NET 时,我通常在 VB.NET 中编码,因此Key可能会以不同的方式转换为 C#。

This is my solution, it supports keySelectors of different types:这是我的解决方案,它支持不同类型的 keySelectors:

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

To use it:要使用它:

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

Distinct method returns distinct elements from a sequence. Distinct方法从序列中返回不同的元素。

If you take a look on its implementation with Reflector, you'll see that it creates DistinctIterator for your anonymous type.如果您使用 Reflector 查看它的实现,您会看到它为您的匿名类型创建了DistinctIterator Distinct iterator adds elements to Set when enumerating over collection.不同的迭代器在枚举集合时向Set添加元素。 This enumerator skips all elements which are already in Set .此枚举器跳过已在Set所有元素。 Set uses GetHashCode and Equals methods for defining if element already exists in Set . Set使用GetHashCodeEquals方法来定义元素是否已存在于Set

How GetHashCode and Equals implemented for anonymous type?如何为匿名类型实现GetHashCodeEquals As it stated on msdn :正如它在msdn 上所说:

Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashcode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal.匿名类型上的 Equals 和 GetHashCode 方法是根据属性的 Equals 和 GetHashcode 方法定义的,同一匿名类型的两个实例仅当它们的所有属性都相等时才相等。

So, you definitely should have distinct anonymous objects, when iterating on distinct collection.所以,在迭代不同的集合时,你绝对应该有不同的匿名对象。 And result does not depend on how many fields you use for your anonymous type.结果不取决于您为匿名类型使用了多少字段。

Answering the headline of the question (what attracted people here) and ignoring that the example used anonymous types....回答问题的标题(这里吸引人们的是什么)并忽略该示例使用匿名类型....

This solution will also work for non-anonymous types.此解决方案也适用于非匿名类型。 It should not be needed for anonymous types.匿名类型不应该需要它。

Helper class:辅助类:

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

Simplest usage:最简单的用法:

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

The simplest (but not that efficient) usage is to map to a string representation so that custom hashing is avoided.最简单(但不是那么有效)的用法是映射到字符串表示,从而避免自定义散列。 Equal strings already have equal hash codes.相等的字符串已经具有相等的哈希码。

Reference:参考:
Wrap a delegate in an IEqualityComparer 在 IEqualityComparer 中包装一个委托

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;

    }

the solution to your problem looks like this:您的问题的解决方案如下所示:

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

Update for .net 6, DistinctBy was added: .net 6 更新,添加了DistinctBy

myQueryable.DistinctBy(c => new { c.KeyA, c.KeyB});

https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable.distinctby?view=net-6.0 https://docs.microsoft.com/en-us/dotnet/api/system.linq.queryable.distinctby?view=net-6.0

(for both IQueryable and IEnumerable ) (对于IQueryableIEnumerable

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM