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


75

我有以下来自数据库的EF类(简化)

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

ProductId是表的主键

对于数据库设计者做出的错误设计决定(我无法修改),我在表中有CategoryIdCategoryName

我需要一个DropDownList的使用(不同)CategoryId价值,并CategoryName文本。因此,我应用了以下代码:

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

从逻辑上讲,它应该使用CategoryIdCategoryName作为属性创建一个匿名对象。在Distinct()保证有没有重复对(CategoryIdCategoryName)。

但实际上它不起作用。就我所理解的Distinct()作品而言,收藏中只有一个领域,否则就忽略了它们……对吗?有什么解决方法吗?谢谢!

更新

对不起,product是:

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

我找到了一种获得与以下结果相同的替代方法Distinct()

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

“集合中只有一个领域”是荒谬的。你什么意思?
嬉皮

1
@leppie我的猜测是,他的意思是投影到单个值而不是匿名类型(包含多个字段)时。
sehe 2012年

“由于数据库设计人员做出了错误的设计决定(我无法对其进行修改)”。您也许无法更改数据库,但这并不意味着您无法在EF模型中解决此问题。这就是EF的优势。
史蒂文

它在哪里不起作用?您在哪里:经典的asp.net,mvc?product.Select中的“产品”是什么?
拉斐尔·阿尔特豪斯

@leppie,对不起,我的意思是“收藏中的财产”
CiccioMiami 2012年

Answers:


70

我假设您在列表上使用非重复的方法调用。您需要将查询结果用作DropDownList的数据源,例如,通过实例化它ToList

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

如果您需要真实的对象而不是仅具有少量属性的匿名类型,则另一种方法是使用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();

第三种选择是使用MoreLinq的DistinctBy


1
@CiccioMiami支持者,请确保您仅将此答案应用于匿名类型。类型化的类可能需要像这样的委托。
crokusek

正确,鲜明的将工作只与匿名类型
imdadhusen

1
@imdadhusen:不仅使用匿名类型,该类型还必须覆盖Equals+,GetHashCode否则您必须IEQualityCpomparer<TypeName>在重载中提供自定义Distinct
蒂姆·施密特

@Tim,我可以一起使用Where和Group By条件吗?在您的第二个查询或代码中,我只想在分组依据之前应用where条件,所以可能吗?
阿德兰医师

11

Distinct()保证没有重复对(CategoryId,CategoryName)。

-就是那个

匿名类型“神奇地”实现EqualsGetHashcode

我认为某个地方有另一个错误。区分大小写吗?可变类?不可比的领域?


那就是我的意思,但是下面的答案说不。/我很困惑。
嬉皮


4

不同方法从序列中返回不同元素。

如果您看一下它在Reflector上的实现,您会发现它是DistinctIterator为您的匿名类型创建的。不同的迭代器Set在枚举集合时向中添加元素。此枚举器将跳过所有已在中的元素Set。定义元素是否已存在的Set用途GetHashCodeEquals方法Set

如何GetHashCodeEquals匿名类型实现?如在msdn上所述

匿名类型上的Equals和GetHashCode方法是根据属性的Equals和GetHashcode方法定义的,只有所有匿名属性都相等时,相同匿名类型的两个实例才相等。

因此,在对不同的集合进行迭代时,您绝对应该具有不同的匿名对象。结果不取决于您用于匿名类型的字段数。


4

Key在您选择的关键字中使用该关键字将起作用,如下所示。

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

我意识到这是一个老话题,但认为这可能对某些人有所帮助。我通常在使用.NET时在VB.NET中进行编码,因此Key可能会不同地转换为C#。


3
为了在VB.Net中的多个字段上执行不同的操作,必须为匿名类型中的每个属性使用“关键字”关键字。(否则,将无法正确计算用于比较的哈希码)在C#中,没有'Key'关键字-而是匿名类型中的所有属性自动成为Key字段。
voon

4

回答问题的标题(什么吸引了这里的人们),而忽略了该示例使用匿名类型...。

该解决方案也适用于非匿名类型。对于匿名类型,不需要它。

助手类:

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

最简单的用法:

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

最简单(但不是那么有效)的用法是映射到字符串表示形式,以避免自定义哈希。相等的字符串已经具有相等的哈希码。

参考:
将委托包装在IEqualityComparer中


4

这是我的解决方案,它支持不同类型的keySelector:

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

要使用它:

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

1
这种方法的优点是,它可以同时用于匿名和已定义类型。
crokusek

在.net 4.7.2中使用此解决方案遇到了麻烦。也许这仅适用于.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;

    }

0

解决问题的方法如下:

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

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

如果我现在想按年龄订购该怎么办?
Si8
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.