Agrupar por várias colunas

967

Como posso fazer GroupBy várias colunas no LINQ

Algo semelhante a isso no SQL:

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

Como posso converter isso para 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
fonte

Respostas:

1213

Use um tipo anônimo.

Por exemplo

group x by new { x.Column1, x.Column2 }
leppie
fonte
29
Se você é novo no agrupamento de tipos anônimos, o uso da palavra-chave 'new' neste exemplo faz a mágica.
22413 Chris
8
no caso de mvc com nHibernate recebendo erro para problemas de DLL. Problema resolvido por GroupBy (x => new {x.Column1, x.Column2}, (chave, grupo) => new {Key1 = key.Column1, Key2 = key.Column2, Result = group.ToList ()});
Milan
Eu pensei que, neste caso, os novos objetos seriam comparados por referência, portanto, não há correspondência - não há agrupamento.
HoGo 19/01
4
Os objetos digitados anônimos do @HoGo implementam seus próprios métodos Equals e GetHashCode , que são usados ​​ao agrupar os objetos.
Byron Carasco
Um pouco difícil de visualizar a estrutura de dados de saída quando você é novo no Linq. Isso cria um agrupamento em que o tipo anônimo é usado como chave?
Jacques
760

Amostra processual

.GroupBy(x => new { x.Column1, x.Column2 })
Mo0gles
fonte
Que tipo é o objeto retornado?
mggSoft
5
@MGG_Soft que seria um tipo anônimo
Alex
Este código não está funcionando para mim: "Declarador de tipo anônimo inválido".
Thalesfc 07/07/2014
19
@ Tom isso deve funcionar do jeito que está. Quando você pula a nomeação dos campos de um tipo anônimo C #, assume que deseja usar o nome da propriedade / campo finalmente acessado da projeção. (Então, seu exemplo é equivalente a Mo0gles')
Chris Pfohl
1
Encontrei minha resposta. Eu preciso definir uma nova entidade (MyViewEntity) contendo as propriedades Column1 e Column2 e o tipo de retorno é: IEnumerable <IGrouping <MyViewEntity, MyEntity >> e snip de código de agrupamento é: MyEntityList.GroupBy (myEntity => new MyViewEntity {Column1 = myEntity. Coluna1, Coluna2 = myEntity.Column2});
Amir Chatrbahr 17/03/2015
469

Ok, entendi como:

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
fonte
75
+1 - Obrigado pelo exemplo abrangente. Os trechos da outra resposta são muito curtos e sem contexto. Além disso, você mostra uma função agregada (soma neste caso). Muito útil. Acho que o uso de uma função agregada (por exemplo, MAX, MIN, SUM etc.) lado a lado com o agrupamento é um cenário comum.
barrypicker
Aqui: stackoverflow.com/questions/14189537/… , é mostrado para uma tabela de dados quando o agrupamento é baseado em uma única coluna, cujo nome é conhecido, mas como isso pode ser feito se as colunas com base nas quais o agrupamento deve ser feito tem que ser gerado dinamicamente?
bg
Isso é realmente útil para entender o conceito de agrupar e aplicar agregação sobre ele.
rajibdotnet
1
Grande exemplo ... exatamente o que eu estava procurando. Eu até precisava do agregado, então essa foi a resposta perfeita, embora eu estivesse procurando por lambda, consegui o suficiente para resolver minhas necessidades.
Kevbo 28/08/18
153

Para Agrupar por várias colunas, tente fazer isso ...

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

Da mesma maneira que você pode adicionar Coluna3, Coluna4 etc.

Milão
fonte
4
Isso foi muito útil e deve receber muito mais votos! Resultcontém todos os conjuntos de dados vinculados a todas as colunas. Muito obrigado!
precisa saber é o seguinte
1
nota: eu tive que usar .AsEnumerable () em vez de ToList ()
GMan
1
Incrível, obrigado por isso. Aqui está o meu exemplo. Observe que GetFees retorna um IQueryable <Fee> RegistryAccountDA.GetFees (registryAccountId, fromDate, toDate) .GroupBy (x => new {x.AccountId, x.FeeName}, (key, group) => new {AccountId = key.AccountId , FeeName = key.FeeName, AppliedFee = group.Sum (x => x.AppliedFee) ?? 0M}). ToList ();
Craig B
É possível obter outras colunas desta consulta, que não foram agrupadas? Se houver uma matriz de objetos, eu gostaria de agrupar esse objeto por duas colunas, mas obter todas as propriedades do objeto, não apenas essas duas colunas.
FrenkyB
30

Desde o C # 7, você também pode usar tuplas de valor:

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

ou

.GroupBy(x => (x.Column1, x.Column2))
Nathan Tregillus
fonte
2
Bem, acho que está faltando um extra) no final. Você não está fechando o ()
StuiterSlurf
Eu adicionei.
#
2
.GroupBy (x => new {x.Column1, x.Column2})
Zahidul Islam Jamy
19

C # 7.1 ou superior usando Tuplesand Inferred tuple element names(atualmente funciona apenas com linq to objectse não é suportado quando árvores de expressão são necessárias, por exemplo someIQueryable.GroupBy(...). Problema no 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 ou superior usando 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
fonte
18

Você também pode usar uma Tupla <> para um agrupamento fortemente tipado.

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),
}
Jay Bienvenu
fonte
4
Nota: A criação de um novo Tuple não é suportado em Entidades LINQ para
foolmoron
8

Embora esta pergunta esteja perguntando sobre agrupar por propriedades de classe, se você deseja agrupar por várias colunas em um objeto ADO (como uma DataTable), você deve atribuir seus "novos" itens a variáveis:

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);
Chris Smith
fonte
1
Não pude fazer a consulta Linq "group c por novos {c.Field <>> (" Title "), c.Field <> (" CIF ")}" e você me salvou muito tempo !! a consulta final foi: "grupo c por novo {titulo = c.Field <>> (" Título "), cif = c.Field <>> (" CIF ")}"
netadictos
4
var Results= query.GroupBy(f => new { /* add members here */  });
Arindam
fonte
7
Não adiciona nada às respostas anteriores.
Mike Fuchs
4
.GroupBy(x => x.Column1 + " " + x.Column2)
Kai Hartmann
fonte
Combinado com Linq.Enumerable.Aggregate()isso ainda permite agrupar por um número dinâmico de propriedades: propertyValues.Aggregate((current, next) => current + " " + next).
Kai Hartmann #
3
Esta é uma resposta melhor do que qualquer um está dando crédito. Pode ser problemático se houver casos de combinações em que a coluna1 adicionada à coluna2 seja igual para as situações em que a coluna1 for diferente ("ab" "cde" corresponderia a "abc" "de"). Dito isto, esta é uma ótima solução se você não pode usar um tipo dinâmico, porque está pré-construindo lambdas após o grupo em expressões separadas.
Brandon Barkley
3
"ab" "cde" na verdade não deve corresponder a "abc" "de", daí o espaço em branco no meio.
Kai Hartmann
1
e quanto a "abc de" "" e "abc" "de"?
AlbertK
@ AlbertK que não vai funcionar, eu acho.
Kai Hartmann
3

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

Vamos acender
fonte
3
Considere adicionar uma explicação sobre como esse código resolve o problema.
Rosário Pereira Fernandes
2

agrupar x por novos {x.Col, x.Col}

John
fonte
1

Um ponto a ser observado é que você precisa enviar um objeto para expressões Lambda e não pode usar uma instância para uma classe.

Exemplo:

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

    public string Prop2 { get; set; }
}

Isso será compilado, mas gerará uma chave por ciclo .

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

Se você não quiser nomear as propriedades-chave e depois recuperá-las, poderá fazê-lo assim. Isso funcionará GroupBycorretamente e fornecerá as principais propriedades.

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
fonte
0

Para VB e anônimo / lambda :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
Dani
fonte