Подтвердить что ты не робот

Как использовать 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})
4b9b3361

Ответ 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();