Группировать по нескольким столбцам

968

Как я могу сделать несколько столбцов GroupBy в LINQ

Нечто похожее на это в SQL:

SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>

Как я могу преобразовать это в LINQ:

QuantityBreakdown
(
    MaterialID int,
    ProductID int,
    Quantity float
)

INSERT INTO @QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM @Transactions
GROUP BY MaterialID, ProductID
SREEDHAR
источник

Ответы:

1213

Используйте анонимный тип.

Например

group x by new { x.Column1, x.Column2 }
leppie
источник
29
Если вы новичок в группировке с анонимными типами, использование ключевого слова 'new' в этом примере делает волшебство.
Крис
8
в случае MVC с nHibernate получить ошибку для проблем с DLL. Проблема решена с помощью GroupBy (x => new {x.Column1, x.Column2}, (ключ, группа) => new {Key1 = key.Column1, Key2 = key.Column2, Result = group.ToList ()});
Милан
Я думал, что в этом случае новые объекты будут сравниваться по ссылке, поэтому нет совпадений - нет группирования.
HoGo
4
@HoGo анонимно типизированные объекты реализуют свои собственные методы Equals и GetHashCode, которые используются при группировке объектов.
Байрон Караско
Немного сложно визуализировать структуру выходных данных, когда вы новичок в Linq. Создает ли это группировку, в которой анонимный тип используется в качестве ключа?
Жак
760

Процессуальный образец

.GroupBy(x => new { x.Column1, x.Column2 })
Mo0gles
источник
Какого типа объект возвращается?
mggSoft
5
@MGG_Soft, это был бы анонимный тип
Алекс
Этот код не работает для меня: «Неверный анонимный тип объявления».
thalesfc
19
@ Это должно работать так, как есть. Когда вы пропускаете именование полей анонимного типа C #, предполагается, что вы хотите использовать имя окончательно доступного свойства / поля из проекции. (Так что ваш пример эквивалентен Mo0gles ')
Крис Пфол
1
нашел мой ответ. Мне нужно определить новую сущность (MyViewEntity), содержащую свойства Column1 и Column2, и тип возвращаемого значения: IEnumerable <IGrouping <MyViewEntity, MyEntity >> и фрагмент кода группировки: MyEntityList.GroupBy (myEntity => new MyViewEntity {Column1 = myEntity). Column1, Column2 = myEntity.Column2});
Амир Чатрбар
469

Хорошо, получил это как:

var query = (from t in Transactions
             group t by new {t.MaterialID, t.ProductID}
             into grp
                    select new
                    {
                        grp.Key.MaterialID,
                        grp.Key.ProductID,
                        Quantity = grp.Sum(t => t.Quantity)
                    }).ToList();
SREEDHAR
источник
75
+1 - Спасибо за исчерпывающий пример. Фрагменты другого ответа слишком короткие и без контекста. Также вы показываете статистическую функцию (в данном случае Sum). Очень полезно. Я считаю, что использование агрегатной функции (т.е. MAX, MIN, SUM и т. Д.) Наряду с группировкой является распространенным сценарием.
barrypicker
Здесь: stackoverflow.com/questions/14189537/… , это показано для таблицы данных, когда группировка основана на одном столбце, имя которого известно, но как это можно сделать, если столбцы, на основе которых должна быть выполнена группировка должен генерироваться динамически?
BG
Это действительно полезно для понимания концепции группировки и применения к ней агрегирования.
Раджибдотнет
1
Отличный пример ... как раз то, что я искал. Я даже нуждался в совокупности, так что это был идеальный ответ, хотя я искал лямбду, я получил достаточно от нее, чтобы решить свои потребности.
Кевбо
153

Для группировки по нескольким столбцам попробуйте это вместо ...

GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new 
{ 
  Key1 = key.Column1,
  Key2 = key.Column2,
  Result = group.ToList() 
});

Таким же образом вы можете добавить Column3, Column4 и т. Д.

Милан
источник
4
Это было очень полезно и должно было получить гораздо больше голосов! Resultсодержит все наборы данных, связанные со всеми столбцами. Большое спасибо!
16:00
1
примечание: я должен был использовать .AsEnumerable () вместо ToList ()
GMan
1
Круто, спасибо за это. Вот мой пример. Обратите внимание, что GetFees возвращает IQueryable <Fee> RegistryAccountDA.GetFees (registryAccountId, fromDate, toDate) .GroupBy (x => new {x.AccountId, x.FeeName}, (ключ, группа) => new {AccountId = key.AccountId , FeeName = key.FeeName, AppliedFee = group.Sum (x => x.AppliedFee) ?? 0M}). ToList ();
Крейг Б
Можно ли получить другие столбцы из этого запроса, которые не были сгруппированы? Если есть массив объектов, я бы хотел сгруппировать этот объект по двум столбцам, но получить все свойства объекта, а не только эти два столбца.
FrenkyB
30

Начиная с C # 7 вы также можете использовать кортежи значений:

group x by (x.Column1, x.Column2)

или

.GroupBy(x => (x.Column1, x.Column2))
Натан Трегиллус
источник
2
Ну, я думаю, вам не хватает дополнительного) в конце. Вы не закрываете ()
StuiterSlurf
Я добавил это.
Натан Трегиллус
2
.GroupBy (x => new {x.Column1, x.Column2})
Захидул Ислам Джеми
19

C # 7.1 или выше с использованием Tuplesи Inferred tuple element names( в настоящее время он работает только с linq to objectsи она не поддерживается , когда деревья выражений необходимы , например someIQueryable.GroupBy(...). Github вопрос ):

// declarative query syntax
var result = 
    from x in inMemoryTable
    group x by (x.Column1, x.Column2) into g
    select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));

// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
    .Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));

C # 3 или выше, используя anonymous types:

// declarative query syntax
var result3 = 
    from x in table
    group x by new { x.Column1, x.Column2 } into g
    select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };

// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
    .Select(g => 
      new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
AlbertK
источник
18

Вы также можете использовать Tuple <> для строго типизированной группировки.

from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
    LastName = grouping.Key.Item1,
    FirstName = grouping.Key.Item2,
    MiddleName = grouping.Key.Item3,
    DayCount = grouping.Count(), 
    AmountBilled = grouping.Sum(x => x.Rate),
}
Джей Биенвену
источник
4
Примечание: Создание нового кортежа не поддерживается в Linq To Entities
foolmoron
8

Хотя этот вопрос задает вопрос о свойствах группировки по классам, если вы хотите сгруппировать по нескольким столбцам объект ADO (например, DataTable), вы должны назначить свои «новые» элементы переменным:

EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
                        .Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
                    var Dups = ClientProfiles.AsParallel()
                        .GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
                        .Where(z => z.Count() > 1)
                        .Select(z => z);
Крис Смит
источник
1
Я не смог выполнить запрос Linq "группа c по новым {c.Field <String> (" Title "), c.Field <String> (" CIF ")}", и вы сэкономили мне много времени !! последний запрос был: «группа c by new {titulo = c.Field <String> (« Заголовок »), cif = c.Field <String> (« CIF »)}»
netadictos
4
var Results= query.GroupBy(f => new { /* add members here */  });
Arindam
источник
7
Ничего не добавляет к предыдущим ответам.
Майк Фукс
4
.GroupBy(x => x.Column1 + " " + x.Column2)
Кай Хартманн
источник
В сочетании с Linq.Enumerable.Aggregate()этим даже позволяет группировать динамическим рядом свойств: propertyValues.Aggregate((current, next) => current + " " + next).
Кай Хартманн
3
Это лучший ответ, чем кто-либо другой. Это может быть проблематично, если могут быть случаи комбинаций, когда column1, добавленный в column2, будет одинаковым для ситуаций, когда column1 отличается («ab» «cde» будет соответствовать «abc» «de»). Тем не менее, это отличное решение, если вы не можете использовать динамический тип, потому что вы предварительно конструируете лямбда-выражения после группы в отдельных выражениях.
Брэндон Баркли
3
«ab» «cde» на самом деле не должно совпадать с «abc» «de», отсюда и пробел между ними.
Кай Хартманн
1
как насчет "abc de" "" и "abc" "de"?
AlbertK
@ AlbertK это не сработает, наверное.
Кай Хартманн
3

.GroupBy(x => (x.MaterialID, x.ProductID))

Давайте зажигать
источник
3
Попробуйте добавить объяснение того, как этот код решает проблему.
Росарио Перейра Фернандес
1

Следует отметить, что вам нужно отправить объект для лямбда-выражений, и вы не можете использовать экземпляр для класса.

Пример:

public class Key
{
    public string Prop1 { get; set; }

    public string Prop2 { get; set; }
}

Это скомпилирует, но сгенерирует один ключ за цикл .

var groupedCycles = cycles.GroupBy(x => new Key
{ 
  Prop1 = x.Column1, 
  Prop2 = x.Column2 
})

Если вы не хотите называть ключевые свойства и затем извлекать их, вы можете сделать это следующим образом. Это GroupByправильно и даст вам ключевые свойства.

var groupedCycles = cycles.GroupBy(x => new 
{ 
  Prop1 = x.Column1, 
  Prop2= x.Column2 
})

foreach (var groupedCycle in groupedCycles)
{
    var key = new Key();
    key.Prop1 = groupedCycle.Key.Prop1;
    key.Prop2 = groupedCycle.Key.Prop2;
}
Ogglas
источник
0

Для VB и анонимных / лямбда :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
Дани
источник