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

我有从数据库派生的以下EF类 (简化)

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

ProductId是表格的主键

对于由数据库devise人员做出的糟糕的devise决定(我无法修改它),我在这个表中有CategoryIdCategoryName

我需要一个DropDownList (独特) CategoryId作为CategoryName作为文本 。 所以我申请了下面的代码:

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

这在逻辑上应该创build一个具有CategoryIdCategoryName属性的匿名对象。 Distinct()保证没有重复对( CategoryIdCategoryName )。

但实际上这是行不通的。 就我所了解的Distinct()作品,只有一个字段在集合中,否则它只是忽略它们…是否正确? 有没有解决办法? 谢谢!

UPDATE

对不起, 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}) 

我假设你使用不同的方法调用列表。 您需要使用查询的结果作为DropDownList的数据源,例如通过ToList实现它。

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

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

– 完全一样

匿名types“神奇地”实现EqualsGetHashcode

我假设另一个错误的地方。 区分大小写? 可变类? 非可比较的领域?

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

如果你用Reflector看看它的实现,你会看到它为你的匿名types创buildDistinctIterator 。 枚举集合时,不同的迭代器将元素添加到Set 。 这个枚举器将跳过已经在Set所有元素。 Set使用GetHashCodeEquals方法来定义元素是否已经存在于Set

GetHashCodeEquals如何实现匿名types? 正如它在msdn上所说:

匿名types的Equals和GetHashCode方法是根据属性的Equals和GetHashcode方法定义的,相同匿名types的两个实例只有在它们的所有属性相等时才相等。

所以,当迭代不同的集合时,你肯定应该有不同的匿名对象。 结果并不取决于您为匿名types使用多less个字段。

在你的select使用Key关键字将工作,如下所示。

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

我意识到这是提出一个老线索,但认为这可能会帮助一些人。 我一般使用.NET编写VB.NET代码,所以Key可能会以不同的方式转换成C#。

回答这个问题的标题(什么吸引了这里的人),并忽略了这个例子使用匿名types….

此解决scheme也适用于非匿名types。 匿名types不应该被需要。

帮手类:

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

最简单的用法:

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

最简单(但效率不高)的用法是映射到string表示,以避免自定义哈希。 相等的string已经有相同的散列码。

参考:
将一个委托包装在IEqualityComparer中

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