Как включить поле List <string> в GroupBy ()

Aug 19 2020

У меня есть список объектов Diff. Diff выглядит так

public class Diff
{
    ChangeAction Action // ChangeAction is an Enum
    string Type
    DateTime StartDateTime
    DateTime EndDateTime
    int rateId
    string rateDescription
    List<string> properties
    DayOfWeek day
    List<DaysOfWeek> DaysOfWeek
    DayOfWeek DayOfWeek

}

Мой запрос LINQ не выполняет то, что я думаю. Я передаю в diff.propertiesв GroupBy(), который является списком , и я хочу его группу , когда все строковые значения в матче списка

var results = diffs
    .GroupBy(diff => new { diff.properties, diff.Action, diff.Type, diff.StartDateTime, 
                           diff.EndDateTime, diff.rateId, diff.rateDescription}) 
    .Select(group => new Diff(
        group.Key.Action,
        group.Key.ScheduleType,
        group.Key.StartDateTime,
        group.Key.EndDateTime,
        group.Key.rateId,
        group.Key.rateDescription,
        group.Key.properties,
        group
            .Select(ts => ts.DayOfWeek)
            .Distinct()
            .OrderBy(dow => dow)
            .ToList()))
    .ToList();

Единственное различие между resultsи diffsзаключается в том, что единственное число, DayOfWeekкоторое ранее было сохранено, diffsтеперь помещается в DaysOfWeekполе множественного числа (но только 1 элемент в списке). В настоящее время одинаковое количество элементов в resultsи diffs.

Что я бы хотел видеть в списке результатов:

  1. Более короткий список, который объединяется на основе сопоставления по всем группам (включая значения списка diff.properties)
  2. Это также будет означать более 1 элемента в списке DaysOfWeek.

У меня вопрос:

Как я могу изменить свой запрос LINQ выше, чтобы увидеть, что я хочу видеть results?

Ответы

4 TravisJ Aug 19 2020 at 06:02

Группировка, которую вы используете с анонимным типом, содержит, List<string>что приводит к получению разгруппированного набора 1-1.

Вам нужно либо

  • использовать пользовательский класс для группировки и перегрузки , GetHashCodeи , Equalsкак показано в этом вопросе: Использование LINQ GroupBy к группе объектов путем ссылки вместо объектов значений

- ИЛИ ЖЕ -

  • составить строку из выбранных значений или их подмножества ( diff.properties, diff.Action, diff.Type, diff.startdatetime, diff.enddatetime, diff.rateId, diff.rateDescription), которая будет служить уникальным ключом для группировки с
2 RufusL Aug 19 2020 at 07:16

Некоторые из ваших GroupByсвойств являются ссылочными типами, а компаратором по умолчанию для этих типов является сравнение ссылок, поэтому ни одно из них никогда не будет совпадать. Чтобы преодолеть это, мы можем написать свои собственные EqualityComparerдля Diffкласса, чтобы мы могли сравнивать их по-своему:

public class DiffEqualityComparer : IEqualityComparer<Diff>
{
    public bool Equals(Diff first, Diff second)
    {
        if (first == null || second == null) return ReferenceEquals(first, second);

        if (first.Properties == null && second.Properties != null) return false;
        if (first.Properties != null && second.Properties == null) return false;
        if (first.Properties != null && second.Properties != null &&
            !first.Properties.OrderBy(p => p)
                .SequenceEqual(second.Properties.OrderBy(p => p)))
            return false;
        if (!first.Action.Equals(second.Action)) return false;
        if (!string.Equals(first.Type, second.Type)) return false;
        if (!first.Start.Equals(second.Start)) return false;
        if (!first.End.Equals(second.End)) return false;
        if (!first.RateId.Equals(second.RateId)) return false;
        if (!string.Equals(first.RateDescription, second.RateDescription)) return false;

        return true;
    }

    public int GetHashCode(Diff obj)
    {
        var hash = obj.Properties?.Aggregate(0,
            (accumulator, current) => accumulator * 17 + current.GetHashCode()) ?? 0;
        hash = hash * 17 + obj.Action.GetHashCode();
        hash = hash * 17 + obj.Type?.GetHashCode() ?? 0;
        hash = hash * 17 + obj.Start.GetHashCode();
        hash = hash * 17 + obj.End.GetHashCode();
        hash = hash * 17 + obj.RateId.GetHashCode();
        hash = hash * 17 + obj.RateDescription?.GetHashCode() ?? 0;

        return hash;
    }
}

И, наконец, мы можем использовать этот настраиваемый компаратор в нашем GroupByметоде:

var results = diffs
    .GroupBy(diff => new DiffEqualityComparer())
    .Select( // rest of code omitted 
1 surprised_ferret Aug 19 2020 at 06:48

Я решил это!

Чтение еще одного вопроса и комментариев + ответов в этом вопросе помогло мне разобраться!

public class DiffComparer : IEqualityComparer<Diff>
    {
        public bool Equals(Diff x, Diff y)
        {
            return x.Action == y.Action &&
                x.Type == y.Type &&
                x.StartDateTime == y.StartDateTime &&
                x.EndDateTime == y.EndDateTime &&
                x.rateId== y.rateId &&
                x.rateDescription == y.rateDescription &&
                x.properties.SequenceEqual(y.properties);
        }

        public int GetHashCode(Diff x)
        {
            int hash = 17;

            hash = hash * 23 + x.Action.GetHashCode();
            hash = hash * 23 + x.Type.GetHashCode();
            hash = hash * 23 + x.StartDateTime .GetHashCode();
            hash = hash * 23 + x.EndDateTime.GetHashCode();
            hash = hash * 23 + x.rateId.GetHashCode();
            hash = hash * 23 + x.rateDescription.GetHashCode();

            foreach (string prop in x.properties)
            {
                hash = hash * 31 + prop.GetHashCode();
            }

            return hash;
        }
    }

И я внес следующие изменения в LINQ:


var results = diffs
    .GroupBy(diff => diff, new DiffComparer()) 
    .Select(group => new Diff(
        group.Key.Action,
        group.Key.ScheduleType,
        group.Key.StartDateTime,
        group.Key.EndDateTime,
        group.Key.rateId,
        group.Key.rateDescription,
        group.Key.properties,
        group
            .Select(ts => ts.DayOfWeek)
            .Distinct()
            .OrderBy(dow => dow)
            .ToList()))
    .ToList();