Używanie Linq do grupowania listy obiektów w nową, zgrupowaną listę obiektów

149

Nie wiem, czy jest to możliwe w Linq, ale oto idzie ...

Mam obiekt:

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

Zwracam listę, która może wyglądać następująco:

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

Chcę mieć możliwość uruchomienia zapytania Linq na powyższej liście, która grupuje wszystkich użytkowników według GroupID. Zatem wynikiem będzie lista list użytkowników, która zawiera użytkownika (jeśli ma to sens?). Coś jak:

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

Próbowałem użyć klauzuli groupby linq, ale wydaje się, że zwraca listę kluczy i nie jest ona poprawnie pogrupowana:

var groupedCustomerList = userList.GroupBy( u => u.GroupID ).ToList();
lancscoder
źródło

Odpowiedzi:

309
var groupedCustomerList = userList
    .GroupBy(u => u.GroupID)
    .Select(grp => grp.ToList())
    .ToList();
Zawietrzny
źródło
1
bardzo ładne, właśnie to, czego potrzebowałem. Dziękuję Ci. robienie tego w sposób imperatywny jest do bani.
user1841243
1
Czy to działa dobrze? ponieważ jestem używany, ten sposób nie zadziałał. objModel.tblDonars.GroupBy (t => new {t.CreatedOn.Year, t.CreatedOn.Month, t.CreatedOn.Day}). Wybierz (g => new {tblDonar = g.ToList ()}). ToList ( ); to nie działa… czy możesz pomóc…
Rajamohan Anguchamy
Twoje rozwiązanie działa dobrze, ale mam jedno pytanie, przypuszczam, że w grupie do 8 wartości i chcę po prostu potrzebować w każdej grupie z zaledwie 6 ujęciami, więc jak to zrobić, daj mi znać.
coderwill
czy istnieje sposób oddzielnego wyszczególnienia nazw użytkowników i identyfikatorów użytkowników po pogrupowaniu ich według identyfikatora grupy? na przykład - {"1", [1, 2, 4], ["UserOne", "UserTwo", "UserFour"]} dla
ID grupy
1
Obecnie kod nie działa i powoduje błąd, jeśli spróbujesz rzutować go na listę poprzedniego typu. Ponieważ zwrócenie listy w select tworzy listy wewnątrz listy, co nie jest tutaj pożądanym wynikiem. Tym, którzy mają problemy, mogę zasugerować: var groupedCustomerList = userList.GroupBy (u => u.GroupID) .Select (grp => grp.First ()). ToList ();
aliassce
36

Twój wyciąg grupowy zostanie pogrupowany według identyfikatora grupy. Na przykład, jeśli napiszesz:

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

to powinno działać dobrze. Każda grupa ma klucz, ale zawiera również IGrouping<TKey, TElement>kolekcję, która umożliwia iterację po członkach grupy. Jak wspomina Lee, możesz przekonwertować każdą grupę na listę, jeśli naprawdę chcesz, ale jeśli zamierzasz je po prostu iterować zgodnie z powyższym kodem, nie ma to żadnych korzyści.

Jon Skeet
źródło
4

Do typu

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

kolekcja

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

nasze zapytanie linq wygląda jak poniżej

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

lub dla tablicy zamiast listy, jak poniżej

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
źródło
-1

Wciąż stara, ale odpowiedź Lee nie podała mi grupy. W rezultacie klucz. Dlatego używam następującej instrukcji, aby pogrupować listę i zwrócić zgrupowaną listę:

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

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

Każda grupa ma teraz klucz, ale zawiera także IGrouping, czyli zbiór, który pozwala na iterację po członkach grupy.

pula danych
źródło
To odpowiada na twoje pytanie, a nie na pytanie OP. Chcą tylko listy list. Twój kod tego nie zapewnia.
Gert Arnold
Opublikowałem to rozwiązanie, ponieważ zaakceptowana powyżej odpowiedź od @Lee nie działa podczas iteracji, ponieważ nie dostarcza group.keyelementu podczas iteracji po zgrupowanej liście, jak pokazano w odpowiedzi @JohnSkeet. Podana przeze mnie odpowiedź zawiera element group.key podczas iteracji. Więc może pomóc innym wpaść w pułapkę, że podane odpowiedzi nie działają tak, jak pokazano powyżej. Jest to więc tylko kolejny sposób na uzyskanie listy z korzyścią uzyskania elementu group.key podobnego do zaakceptowanej odpowiedzi. Nie rozumiem swojego roszczenia @GertArnold. (.net core 3.0)
pula danych
Myślę, że całe pytanie opiera się na braku zrozumienia, co IGroupingjest. Dlatego OP odrzuca swój własny poprawny kod na dole, który jest zasadniczo identyczny z twoim kodem. Dlatego akceptują odpowiedź, która zawiera dokładnie to, o co proszą. Potrzebują wyjaśnienia, które JS dostarczył w wystarczający sposób.
Gert Arnold