[英]How to use LINQ Distinct() with multiple fields
我有以下从数据库派生的EF 类(简化)
class Product
{
public string ProductId;
public string ProductName;
public string CategoryId;
public string CategoryName;
}
ProductId
是表的主键。
对于 DB 设计者做出的错误设计决定(我无法修改它),我在此表中有CategoryId
和CategoryName
。
我需要一个DropDownList与(不同的) CategoryId
作为Value和CategoryName
作为Text 。 因此我应用了以下代码:
product.Select(m => new {m.CategoryId, m.CategoryName}).Distinct();
从逻辑上讲,它应该创建一个具有CategoryId
和CategoryName
作为属性的匿名对象。 Distinct()
保证没有重复对( CategoryId
, CategoryName
)。
但实际上它不起作用。 据我了解, 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})
我假设您使用 distinct 就像对列表的方法调用一样。 您需要将查询结果用作 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
。
Distinct() 保证没有重复对(CategoryId、CategoryName)。
- 正是这样
匿名类型“神奇地”实现了Equals
和GetHashcode
我假设某处有另一个错误。 区分大小写? 可变类? 不可比较的领域?
在您的选择中使用Key
关键字将起作用,如下所示。
product.Select(m => new {Key m.CategoryId, Key m.CategoryName}).Distinct();
我意识到这是提出了一个旧线程,但认为它可能对某些人有所帮助。 在使用 .NET 时,我通常在 VB.NET 中编码,因此Key
可能会以不同的方式转换为 C#。
这是我的解决方案,它支持不同类型的 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;
}
}
}
要使用它:
list.DistinctBy(d => d.CategoryId, d => d.CategoryName)
Distinct方法从序列中返回不同的元素。
如果您使用 Reflector 查看它的实现,您会看到它为您的匿名类型创建了DistinctIterator
。 不同的迭代器在枚举集合时向Set
添加元素。 此枚举器跳过已在Set
所有元素。 Set
使用GetHashCode
和Equals
方法来定义元素是否已存在于Set
。
如何为匿名类型实现GetHashCode
和Equals
? 正如它在msdn 上所说:
匿名类型上的 Equals 和 GetHashCode 方法是根据属性的 Equals 和 GetHashcode 方法定义的,同一匿名类型的两个实例仅当它们的所有属性都相等时才相等。
所以,在迭代不同的集合时,你绝对应该有不同的匿名对象。 结果不取决于您为匿名类型使用了多少字段。
回答问题的标题(这里吸引人们的是什么)并忽略该示例使用匿名类型....
此解决方案也适用于非匿名类型。 匿名类型不应该需要它。
辅助类:
/// <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();
最简单(但不是那么有效)的用法是映射到字符串表示,从而避免自定义散列。 相等的字符串已经具有相等的哈希码。
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;
}
您的问题的解决方案如下所示:
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();
.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
(对于IQueryable
和IEnumerable
)
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.