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);
|