G+

Динамические параметры для GroupBy Linq

Программирование Asp.net
Предыдущий Следующий

Linq - это отличная технология для управления данными.
 
Одной из его особенностей является группировка. Многие люди понимают группировку, как она определена в Sql. Linq осуществляет группировку вполне точно так же. Давайте узнаем этот синтаксис и как сделать последовательную группировку проще.
 
Предположим, что есть коллекция заказчиков: «клиент». Вы должны использовать GroupBy, чтобы определить группы среди ваших данных.

var q = from c in db.Customers
group c by c.Country;

В итоге мы получим перечисление IGrouping. Он просто добавить несколько вещей:
 
- Ключ группы (страна в нашем примере).
- Элементы, сгруппированных этим общим ключом.

В большинстве случаев мы используем группы для получения сумм или количества значений.

var q =
    from g in
        (from c in db.Customers
         group c by c.Country)
    select new { g.Key, Count = g.Count() };

Чтобы упростить этот синтаксис, вы можете использовать ключевое слово "into"

var q =
    from c in db.Customers
    group c by c.Country into g
    select new { g.Key, Count = g.Count() };

Теперь давайте попытаемся создать под-группы внутри этого запроса. Цель проста: я хотел получить группу клиентов по странам, а потом по городам внутри каждой группы.
  Мы можем записать это "вручную":

var q =
    from c in db.Customers
    group c by c.Country into g
    select new {
        g.Key,
        Count = g.Count(),
        SubGroups = from c in g
                   group c by c.City into g2
                   select g2};

В результате получаем дерево элементов, сгруппированных в первом уровне по стране, а затем каждая группа-страна содержить подгруппы городов.
 
  Этого код будет становиться все менее и менее читаемым, когда количество под групп будет расти. Я хотел сделать этот сценарий более простым и более общим. Вот идея.
 

Сначала создадим класс который будет возращать наш запрос. Класс нужен для точного определение группы, его можно вернуть из метода. И метод в итоге будет рекурсивный.

public class GroupResult<T>
    {
        public object Key { get; set; }
        public int Count { get; set; }
        public IEnumerable<T> Items { get; set; }
        public IEnumerable<GroupResult<T>> SubGroups { get; set; }
        public override string ToString()
        { return string.Format("{0} ({1})", Key, Count); }
    }


Хорошо, теперь давайте напишем главную работу. Метод GroupByMany расширяет IEnumerable <T>, так же как и  GroupBy, но вы можете добавить неопределенное число групповых селекторов (params Func <TElement, TKey> [] groupSelectors).
  Если число групповых селекторов равен нулю, то метод возвращает null. Это необходимо также для остановки рекурсии в случае нескольких селекторов.
 
public static class GroupEnumerableExtensions
    {
         public static IEnumerable<GroupResult<TElement>> GroupByMany<TElement>(
            this IEnumerable<TElement> elements,
            params Func<TElement, object>[] groupSelectors)
        {
            if (groupSelectors.Length > 0)
            {
                var selector = groupSelectors.First();
                var nextSelectors = groupSelectors.Skip(1).ToArray();
                return
                    elements.GroupBy(selector).Select(
                        g => new GroupResult<TElement>
                        {
                            Key = g.Key,
                            Count = g.Count(),
                            Items = g,
                            SubGroups = g.GroupByMany(nextSelectors)
                        });
            }
            else
                return null;
    }
}

 

Пример работы:

var result = customers.GroupByMany<TCostomerItem>(c => c.Country, c => c.City);

Комментарий:
Комментариев пока нету
Самостоятельный отпуск Опыт заказа вывоза мусора в Киеве