Agrupar por múltiples columnas

968

¿Cómo puedo hacer GroupBy Multiple Columns en LINQ?

Algo similar a esto en SQL:

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

¿Cómo puedo convertir esto a 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
fuente

Respuestas:

1213

Use un tipo anónimo.

P.ej

group x by new { x.Column1, x.Column2 }
leppie
fuente
29
Si eres nuevo en la agrupación con tipos anónimos, el uso de la palabra clave 'nuevo' en este ejemplo hace la magia.
Chris
8
en caso de mvc con nHibernate obteniendo error por problemas de dll. Problema resuelto por GroupBy (x => new {x.Column1, x.Column2}, (key, group) => new {Key1 = key.Column1, Key2 = key.Column2, Result = group.ToList ()});
Milán
Pensé que en este caso los nuevos objetos se compararían por referencia, por lo que no coincidiría, no se agruparía.
HoGo
44
Los objetos mecanografiados anónimos de @HoGo implementan sus propios métodos Equals y GetHashCode que se utilizan al agrupar los objetos.
Byron Carasco
Un poco difícil de visualizar la estructura de datos de salida cuando eres nuevo en Linq. ¿Esto crea una agrupación donde se usa el tipo anónimo como clave?
Jacques
760

Muestra de procedimiento

.GroupBy(x => new { x.Column1, x.Column2 })
Mo0gles
fuente
¿De qué tipo se devuelve el objeto?
mggSoft 01 de
55
@MGG_Soft que sería un tipo anónimo
Alex
Este código no me funciona: "Declarador de tipo anónimo no válido".
thalesfc
19
@ Tom esto debería funcionar como está. Cuando omite nombrar los campos de un tipo anónimo, C # supone que desea utilizar el nombre de la propiedad / campo finalmente accedido desde la proyección. (Entonces su ejemplo es equivalente a Mo0gles ')
Chris Pfohl
1
Encontré mi respuesta. Necesito definir una nueva entidad (MyViewEntity) que contenga las propiedades Column1 y Column2 y el tipo de retorno es: IEnumerable <IGrouping <MyViewEntity, MyEntity >> y el fragmento de código de agrupación es: MyEntityList.GroupBy (myEntity => new MyViewEntity {Column1 = myEntity. Column1, Column2 = myEntity.Column2});
Amir Chatrbahr
469

Ok tengo esto 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
fuente
75
+1 - Gracias por el ejemplo completo. Los fragmentos de la otra respuesta son demasiado cortos y sin contexto. También muestra una función agregada (Suma en este caso). Muy útil. Encuentro el uso de una función agregada (es decir, MAX, MIN, SUM, etc.) junto con la agrupación como un escenario común.
barrypicker
Aquí: stackoverflow.com/questions/14189537/… , se muestra para una tabla de datos cuando la agrupación se basa en una sola columna, cuyo nombre se conoce, pero ¿cómo se puede hacer si las columnas se basan en la agrupación? tiene que ser generado dinámicamente?
bg
Esto es realmente útil para comprender el concepto de agrupación y aplicación de agregación sobre él.
rajibdotnet
1
Gran ejemplo ... justo lo que estaba buscando. Incluso necesitaba el agregado, así que esta fue la respuesta perfecta, aunque estaba buscando lambda, obtuve suficiente para resolver mis necesidades.
Kevbo
153

Para Agrupar por varias columnas, intente esto en su lugar ...

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

De la misma manera, puede agregar Column3, Column4, etc.

Milán
fuente
44
¡Eso fue muy útil y debería recibir muchos más votos a favor! Resultcontiene todos los conjuntos de datos vinculados a todas las columnas. ¡Muchas gracias!
j00hi
1
nota: tuve que usar .AsEnumerable () en lugar de ToList ()
GMan
1
Impresionante, gracias por esto. Aquí está mi ejemplo. Tenga en cuenta que GetFees devuelve un 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
¿Es posible obtener otras columnas de esta consulta, que no se agruparon? Si hay una matriz de objetos, me gustaría agrupar este objeto en dos columnas, pero obtener todas las propiedades del objeto, no solo esas dos columnas.
FrenkyB
30

Desde C # 7 también puede usar tuplas de valor:

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

o

.GroupBy(x => (x.Column1, x.Column2))
Nathan Tregillus
fuente
2
Bueno, creo que te falta un extra) al final. No está cerrando el ()
StuiterSlurf
Lo he agregado
Nathan Tregillus el
2
.GroupBy (x => new {x.Column1, x.Column2})
Zahidul Islam Jamy
19

C # 7.1 o superior utilizando Tuplesy Inferred tuple element names(actualmente sólo funciona con linq to objectsy no se admite cuando los árboles de expresión se requieren por ejemplo someIQueryable.GroupBy(...). Tema 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 o 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
fuente
18

También puede usar una Tupla <> para una agrupación fuertemente tipada.

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
fuente
44
Nota: Crear una nueva Tupla no es compatible con Linq To Entities
foolmoron
8

Aunque esta pregunta es acerca de las propiedades de agrupar por clase, si desea agrupar por múltiples columnas contra un objeto ADO (como un DataTable), debe asignar sus elementos "nuevos" a las variables:

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
fuente
1
No pude hacer la consulta Linq "grupo c por nuevo {c.Field <String> (" Título "), c.Field <String> (" CIF ")}", ¡y me ahorró mucho tiempo! la consulta final fue: "grupo c por nuevo {titulo = c.Field <String> (" Título "), cif = c.Field <String> (" CIF ")}"
netadictos
4
var Results= query.GroupBy(f => new { /* add members here */  });
Arindam
fuente
77
No agrega nada a las respuestas anteriores.
Mike Fuchs
4
.GroupBy(x => x.Column1 + " " + x.Column2)
Kai Hartmann
fuente
En combinación con Linq.Enumerable.Aggregate()esto incluso permite la agrupación de un número dinámico de propiedades: propertyValues.Aggregate((current, next) => current + " " + next).
Kai Hartmann
3
Esta es una mejor respuesta de la que nadie está dando crédito. Puede ser problemático si podría haber instancias de combinaciones en las que la columna 1 agregada a la columna 2 sería igual para situaciones en las que la columna 1 difiere ("ab" "cde" coincidiría con "abc" "de"). Dicho esto, esta es una gran solución si no puede usar un tipo dinámico porque está preconstruyendo lambdas después del grupo en expresiones separadas.
Brandon Barkley
3
"ab" "cde" en realidad no debería coincidir con "abc" "de", de ahí el espacio en blanco en el medio.
Kai Hartmann,
1
¿Qué pasa con "abc de" "" y "abc" "de"?
AlbertK
@AlbertK eso no funcionará, supongo.
Kai Hartmann
3

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

Enkindle
fuente
3
Considere agregar una explicación sobre cómo este código resuelve el problema.
Rosário Pereira Fernandes
2

grupo x por nuevo {x.Col, x.Col}

Juan
fuente
1

Una cosa a tener en cuenta es que debe enviar un objeto para las expresiones Lambda y no puede usar una instancia para una clase.

Ejemplo:

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

    public string Prop2 { get; set; }
}

Esto compilará pero generará una clave por ciclo .

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

Si no quiere nombrar las propiedades clave y luego recuperarlas, puede hacerlo así. Esto lo hará GroupBycorrectamente y le dará las propiedades clave.

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

Para VB y anónimo / lambda :

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