Usando o Linq para agrupar uma lista de objetos em uma nova lista agrupada de lista de objetos

149

Eu não sei se isso é possível no Linq, mas aqui vai ...

Eu tenho um objeto:

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

Devolvo uma lista que pode parecer com a seguinte:

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

Quero poder executar uma consulta Linq na lista acima que agrupa todos os usuários por GroupID. Portanto, a saída será uma lista de listas de usuários que contêm usuários (se isso faz 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

Eu tentei usar a cláusula groupby linq, mas isso parece retornar uma lista de chaves e não está agrupada corretamente:

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

Respostas:

309
var groupedCustomerList = userList
    .GroupBy(u => u.GroupID)
    .Select(grp => grp.ToList())
    .ToList();
Lee
fonte
1
muito legal, exatamente o que eu precisava. obrigado. fazendo isso da maneira imperativa é uma merda.
user1841243
1
Está funcionando bem? porque eu sou usado dessa maneira não funcionou. objModel.tblDonars.GroupBy (t => novo {t.CreatedOn.Year, t.CreatedOn.Month, t.CreatedOn.Day}). Selecione (g => novo {tblDonar = g.ToList ()}). ToList ( ); isso não está funcionando ... você pode ajuda ....
Rajamohan Anguchamy
com a sua solução, está funcionando bem, mas suponho que uma pergunta no grupo tenha até 8 valores e eu quero apenas precisar em todos os grupos com apenas 6 tomadas, então como fazer isso, por favor me avise.
coderwill
existe uma maneira de listar nomes de usuário e IDs de usuário separadamente depois de agrupar por ID de grupo? como - {"1", [1, 2, 4], ["UserOne", "UserTwo", "UserFour"]} para o groupID 1. #
Ajay Aradhya
1
Atualmente, o código não funciona e causa um erro se você tentar convertê-lo no tipo de lista anterior. Como retornar uma lista em select cria uma lista dentro de uma lista que não é a saída desejada aqui. Para quem tem problemas, posso sugerir: var groupedCustomerList = userList.GroupBy (u => u.GroupID). Selecione (grp => grp.First ()). ToList ();
aliassce
36

Seu grupo declaração vontade grupo, ID de grupo. Por exemplo, se você escrever:

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

isso deve funcionar bem. Cada grupo tem uma chave, mas também contém umaIGrouping<TKey, TElement> que é uma coleção que permite iterar sobre os membros do grupo. Como Lee menciona, você pode converter cada grupo em uma lista, se realmente quiser, mas se você quiser iterar sobre eles conforme o código acima, não há benefício real em fazê-lo.

Jon Skeet
fonte
4

Para o tipo

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

coleção

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" }
};

nossa consulta linq se parece abaixo

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

ou para matriz em vez de lista como abaixo

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

Ainda era antigo, mas a resposta de Lee não me deu o grupo. Portanto, estou usando a seguinte instrução para agrupar uma lista e retornar uma 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 agora tem uma chave, mas também contém um IGrouping, que é uma coleção que permite que você itere sobre os membros do grupo.

datapool
fonte
Isso responde à sua pergunta, não à pergunta do OP. Eles querem apenas uma lista de listas. Seu código não fornece isso.
GER Arnold #
Publiquei esta solução porque a resposta aceita acima do @Lee não funciona ao iterar, pois não fornece o group.keyelemento ao iterar na lista agrupada, como mostrado na resposta do @JohnSkeet. Minha resposta fornecido faz fornecer o elemento group.key quando a iteração. Portanto, pode ajudar outros a cair na armadilha que as respostas fornecidas não funcionem como mostrado acima. Portanto, é apenas mais uma maneira de obter a lista com o benefício de obter o elemento group.key semelhante à resposta aceita. Não entenda sua reivindicação @GertArnold. (.net core 3.0)
datapool 13/11/19
Eu acho que toda a questão se baseia na falta de compreensão do que IGroupingé. É por isso que o OP descarta seu próprio código correto na parte inferior, que é essencialmente idêntico ao seu código. É por isso que eles aceitam uma resposta que fornece exatamente o que pedem. O que eles precisam é de uma explicação, que JS forneceu suficientemente.
Gert Arnold