Usando Linq para agrupar una lista de objetos en una nueva lista agrupada de lista de objetos

149

No sé si esto es posible en Linq, pero aquí va ...

Tengo un objeto:

public class User
{
  public int UserID { get; set; }
  public string UserName { get; set; }
  public int GroupID { get; set; }
}

Devuelvo una lista que puede parecerse a la siguiente:

List<User> userList = new List<User>();
userList.Add( new User { UserID = 1, UserName = "UserOne", GroupID = 1 } );
userList.Add( new User { UserID = 2, UserName = "UserTwo", GroupID = 1 } );
userList.Add( new User { UserID = 3, UserName = "UserThree", GroupID = 2 } );
userList.Add( new User { UserID = 4, UserName = "UserFour", GroupID = 1 } );
userList.Add( new User { UserID = 5, UserName = "UserFive", GroupID = 3 } );
userList.Add( new User { UserID = 6, UserName = "UserSix", GroupID = 3 } );

Quiero poder ejecutar una consulta de Linq en la lista anterior que agrupa a todos los usuarios por GroupID. Por lo tanto, la salida será una lista de listas de usuarios que contiene un usuario (si eso tiene sentido?). Algo como:

GroupedUserList
    UserList
        UserID = 1, UserName = "UserOne", GroupID = 1
        UserID = 2, UserName = "UserTwo", GroupID = 1
        UserID = 4, UserName = "UserFour", GroupID = 1
    UserList
        UserID = 3, UserName = "UserThree", GroupID = 2
    UserList
        UserID = 5, UserName = "UserFive", GroupID = 3
        UserID = 6, UserName = "UserSix", GroupID = 3

Intenté usar la cláusula groupby linq pero parece que devuelve una lista de claves y no está agrupada correctamente:

var groupedCustomerList = userList.GroupBy( u => u.GroupID ).ToList();
lancscoder
fuente

Respuestas:

309
var groupedCustomerList = userList
    .GroupBy(u => u.GroupID)
    .Select(grp => grp.ToList())
    .ToList();
Sotavento
fuente
1
muy lindo, justo lo que necesitaba. gracias. haciendo esto la forma imperativa apesta.
user1841243
1
¿Funciona bien? porque estoy acostumbrado de esta manera no funcionó. objModel.tblDonars.GroupBy (t => new {t.CreatedOn.Year, t.CreatedOn.Month, t.CreatedOn.Day}). Seleccione (g => new {tblDonar = g.ToList ()}). ToList ( ); esto no está funcionando ... ¿pueden ayudar ...
Rajamohan Anguchamy
con su solución funciona bien, pero tengo una pregunta, supongamos que en el grupo tiene un valor de hasta 8 y solo quiero necesitar en cada grupo con solo 6 tomas, así que, ¿cómo puede hacerlo? Avíseme.
coderwill
¿Hay alguna manera de enumerar UserNames y UserIDs por separado después de agrupar por GroupID? como - {"1", [1, 2, 4], ["UserOne", "UserTwo", "UserFour"]} para groupID 1.
Ajay Aradhya
1
Actualmente el código no funciona y causa un error si intenta convertirlo en un tipo de lista anterior. Porque devolver una lista en select crea una lista dentro de una lista que no es el resultado deseado aquí. Para aquellos que tienen problemas, puedo sugerir: var groupedCustomerList = userList.GroupBy (u => u.GroupID) .Select (grp => grp.First ()). ToList ();
aliassce
36

Su declaración de grupo se agrupará por ID de grupo. Por ejemplo, si luego escribe:

foreach (var group in groupedCustomerList)
{
    Console.WriteLine("Group {0}", group.Key);
    foreach (var user in group)
    {
        Console.WriteLine("  {0}", user.UserName);
    }
}

Eso debería funcionar bien. Cada grupo tiene una clave, pero también contiene una IGrouping<TKey, TElement>que es una colección que le permite iterar sobre los miembros del grupo. Como menciona Lee, puede convertir cada grupo en una lista si realmente lo desea, pero si solo va a iterar sobre ellos según el código anterior, no hay ningún beneficio real al hacerlo.

Jon Skeet
fuente
4

Por tipo

public class KeyValue
{
    public string KeyCol { get; set; }
    public string ValueCol { get; set; }
}

colección

var wordList = new Model.DTO.KeyValue[] {
    new Model.DTO.KeyValue {KeyCol="key1", ValueCol="value1" },
    new Model.DTO.KeyValue {KeyCol="key2", ValueCol="value1" },
    new Model.DTO.KeyValue {KeyCol="key3", ValueCol="value2" },
    new Model.DTO.KeyValue {KeyCol="key4", ValueCol="value2" },
    new Model.DTO.KeyValue {KeyCol="key5", ValueCol="value3" },
    new Model.DTO.KeyValue {KeyCol="key6", ValueCol="value4" }
};

nuestra consulta linq se ve a continuación

var query =from m in wordList group m.KeyCol by m.ValueCol into g
select new { Name = g.Key, KeyCols = g.ToList() };

o para la matriz en lugar de la lista como a continuación

var query =from m in wordList group m.KeyCol by m.ValueCol into g
select new { Name = g.Key, KeyCols = g.ToList().ToArray<string>() };
sangram
fuente
-1

Todavía es viejo, pero la respuesta de Lee no me dio el grupo. Clave como resultado. Por lo tanto, estoy usando la siguiente declaración para agrupar una lista y devolver una lista agrupada:

public IOrderedEnumerable<IGrouping<string, User>> groupedCustomerList;

groupedCustomerList =
        from User in userList
        group User by User.GroupID into newGroup
        orderby newGroup.Key
        select newGroup;

Cada grupo ahora tiene una clave, pero también contiene un IGrouping que es una colección que le permite iterar sobre los miembros del grupo.

conjunto de datos
fuente
Esto responde su pregunta, no la pregunta de OP. Solo quieren una lista de listas. Su código no proporciona eso.
Gert Arnold el
Publiqué esta solución porque la respuesta aceptada anteriormente de @Lee no funciona al iterar, ya que no proporciona el group.keyelemento al iterar a través de la lista agrupada como se muestra en la respuesta de @JohnSkeet. Mi respuesta siempre hace proporcionar el elemento group.key cuando se repite. Por lo tanto, podría ayudar a otros caer en la trampa que las respuestas proporcionadas no funcionen como se muestra arriba. Por lo tanto, es solo otra forma de obtener la lista con el beneficio de obtener el elemento group.key similar a la respuesta aceptada. No entienda su reclamo @GertArnold. (.net core 3.0)
datapool
Creo que toda la pregunta se basa en la falta de comprensión de lo que IGroupinges. Es por eso que OP descarta su propio código correcto en la parte inferior, que es esencialmente idéntico a su código. Es por eso que aceptan una respuesta que proporciona exactamente lo que piden. Lo que necesitan es una explicación, que JS proporcionó suficientemente.
Gert Arnold el