C# 如何在多个字段中使用 LINQ Distinct()
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10719928/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use LINQ Distinct() with multiple fields
提问by CiccioMiami
I have the following EF classderived from a database (simplified)
我有以下从数据库派生的EF 类(简化)
class Product
{
public string ProductId;
public string ProductName;
public string CategoryId;
public string CategoryName;
}
ProductIdis the Primary Keyof the table.
ProductId是表的主键。
For a bad design decision made by the DB designer (I cannot modify it), I have CategoryIdand CategoryNamein this table.
对于 DB 设计者做出的错误设计决定(我无法修改它),我在此表中有CategoryId和CategoryName。
I need a DropDownListwith (distinct) CategoryIdas Valueand CategoryNameas Text. Therefore I applied the following code:
我需要一个DropDownList与(不同的)CategoryId作为Value和CategoryName作为Text。因此我应用了以下代码:
product.Select(m => new {m.CategoryId, m.CategoryName}).Distinct();
which logically it should create an anonymous object with CategoryIdand CategoryNameas properties. The Distinct()guarantees that there are no duplicates pair (CategoryId, CategoryName).
从逻辑上讲,它应该创建一个带有CategoryId和CategoryName作为属性的匿名对象。在Distinct()保证有没有重复对(CategoryId,CategoryName)。
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? Is there any workaround? Thanks!
但实际上它不起作用。据我了解,Distinct()当集合中只有一个字段时,它就会忽略它们……这是正确的吗?有什么解决方法吗?谢谢!
UPDATE
更新
Sorry productis:
抱歉的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})
回答by sehe
The Distinct() guarantees that there are no duplicates pair (CategoryId, CategoryName).
Distinct() 保证没有重复对(CategoryId、CategoryName)。
- exactly that
- 正是这样
Anonymous types 'magically' implement Equalsand GetHashcode
匿名类型“神奇地”实现Equals并GetHashcode
I assume another error somewhere. Case sensitivity? Mutable classes? Non-comparable fields?
我假设某处有另一个错误。区分大小写?可变类?不可比较的领域?
回答by Tim Schmelter
I assume that you use distinct like a method call on a list. You need to use the result of the query as datasource for your DropDownList, for example by materializing it via ToList.
我假设您使用 distinct 就像对列表的方法调用一样。您需要将查询结果用作 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 GroupBywith 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.
回答by Sergey Berezovskiy
Distinctmethod returns distinct elements from a sequence.
Distinct方法从序列中返回不同的元素。
If you take a look on its implementation with Reflector, you'll see that it creates DistinctIteratorfor your anonymous type. Distinct iterator adds elements to Setwhen enumerating over collection. This enumerator skips all elements which are already in Set. Setuses GetHashCodeand Equalsmethods for defining if element already exists in Set.
如果您使用 Reflector 查看它的实现,您会看到它是DistinctIterator为您的匿名类型创建的。不同的迭代器Set在枚举集合时添加元素。此枚举器跳过 中已经存在的所有元素Set。Set用途GetHashCode和Equals定义,如果元素已经存在的方法Set。
How GetHashCodeand Equalsimplemented for anonymous type? As it stated on msdn:
如何GetHashCode和Equals实现匿名类型?正如它在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.
所以,在迭代不同的集合时,你绝对应该有不同的匿名对象。结果不取决于您为匿名类型使用了多少字段。
回答by Michael Stramel
Use the Keykeyword in your select will work, like below.
Key在您的选择中使用关键字将起作用,如下所示。
product.Select(m => new {Key m.CategoryId, Key m.CategoryName}).Distinct();
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 Keymay translate differently into C#.
我意识到这是提出了一个旧线程,但认为它可能对某些人有所帮助。在使用 .NET 时,我通常在 VB.NET 中编码,因此Key可能会以不同的方式转换为 C#。
回答by Narendra Kumar Samal
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();
回答by crokusek
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
回答by saktiprasad swain
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;
}
回答by Node.JS
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)
回答by Paul Alexeev
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();

