Как использовать LINQ Distinct() с несколькими полями
У меня есть следующий класс EF, полученный из базы данных (упрощенной)
class Product
{
public string ProductId;
public string ProductName;
public string CategoryId;
public string CategoryName;
}
ProductId
- это Основной ключ таблицы.
Для плохого дизайнерского решения, сделанного разработчиком БД (я не могу его изменить), у меня есть CategoryId
и CategoryName
в этой таблице.
Мне нужен DropDownList с (отличным) CategoryId
как Значение и CategoryName
как Текст. Поэтому я применил следующий код:
product.Select(m => new {m.CategoryId, m.CategoryName}).Distinct();
который логически должен создать анонимный объект с CategoryId
и CategoryName
в качестве свойств. Distinct()
гарантирует отсутствие пары дубликатов (CategoryId
, CategoryName
).
Но на самом деле это не работает. Насколько я понял, 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})
Ответы
Ответ 1
Я предполагаю, что вы используете разные как вызов метода в списке. Вам нужно использовать результат запроса в качестве источника данных для вашего 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
.
Ответ 2
Функция Distinct() гарантирует отсутствие пары дубликатов (CategoryId, CategoryName).
- точно, что
Анонимные типы "магически" реализуют Equals
и GetHashcode
Я предполагаю еще одну ошибку. Чувствительность? Переменные классы? Не сопоставимые поля?
Ответ 3
Distinct метод возвращает отдельные элементы из последовательности.
Если вы посмотрите на его реализацию с помощью Reflector, вы увидите, что он создает DistinctIterator
для вашего анонимного типа. Отдельный итератор добавляет элементы к Set
при перечислении по коллекции. Этот перечислитель пропускает все элементы, которые уже находятся в Set
. Set
использует методы GetHashCode
и Equals
для определения, существует ли элемент в Set
.
Как GetHashCode
и Equals
реализованы для анонимного типа? Как указано на msdn:
Методы Equals и GetHashCode для анонимных типов определяются в терминах методов Equals и GetHashcode свойств, два экземпляра одного и того же анонимного типа равны, только если все их свойства равны.
Итак, у вас определенно должны быть разные анонимные объекты, когда они повторяются в отдельной коллекции. И результат не зависит от того, сколько полей вы используете для своего анонимного типа.
Ответ 4
Используйте ключевое слово Key
в вашем выборе, как показано ниже.
product.Select(m => new {Key m.CategoryId, Key m.CategoryName}).Distinct();
Я понимаю, что это воспитывает старый поток, но полагал, что это может помочь некоторым людям. Я обычно кодирую VB.NET при работе с .NET, поэтому Key
может переводить по-другому на С#.
Ответ 5
Это моё решение, оно поддерживает 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)
Ответ 6
Отвечая на заголовок вопроса (что привлекло людей здесь) и игнорировало, что в примере используются анонимные типы....
Это решение также будет работать для не анонимных типов. Это не должно быть необходимо для анонимных типов.
Класс помощника:
/// <summary>
/// Allow IEqualityComparer to be configured within a lambda expression.
/// From /questions/6617/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().
/// /questions/6617/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
Ответ 7
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;
}
Ответ 8
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();