Grupuj według wielu kolumn


967

Jak mogę wykonać GroupBy wiele kolumn w LINQ

Coś podobnego do tego w SQL:

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

Jak mogę przekonwertować to na 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

Odpowiedzi:


1212

Użyj anonimowego typu.

Na przykład

group x by new { x.Column1, x.Column2 }

29
Jeśli dopiero zaczynasz grupować z anonimowymi typami, użycie słowa kluczowego „new” w tym przykładzie działa magicznie.
Chris,

8
w przypadku mvc z nHibernate pobieranie błędu dla problemów z biblioteką DLL. Problem rozwiązany przez GroupBy (x => new {x.Column1, x.Column2}, (key, group) => new {Key1 = key.Column1, Key2 = key.Column2, Result = group.ToList ()});
Mediolan,

Myślałem, że w tym przypadku nowe obiekty będą porównywane przez odniesienie, więc brak dopasowania - brak grupowania.
HoGo,

4
Anonimowe obiekty @HoGo implementują własne metody Equals i GetHashCode , które są używane podczas grupowania obiektów.
Byron Carasco,

Trochę trudno wizualizować strukturę danych wyjściowych, gdy jesteś nowym użytkownikiem Linq. Czy tworzy to grupę, w której typ anonimowy jest używany jako klucz?
Jacques

760

Próbka proceduralna

.GroupBy(x => new { x.Column1, x.Column2 })

Jakiego typu obiekt jest zwracany?
mggSoft,

5
@MGG_Soft, który byłby typem anonimowym
Alex

Ten kod nie działa dla mnie: „Nieprawidłowy deklarator typu anonimowego”.
thalesfc

19
@ Tom powinno działać tak, jak jest. Po pominięciu nazwania pól anonimowego typu C # zakłada, że ​​chcesz użyć nazwy ostatecznie dostępnej właściwości / pola z projekcji. (Więc twój przykład jest równoważny Mo0gles ')
Chris Pfohl

1
znalazłem moją odpowiedź. Muszę zdefiniować nowy byt (MyViewEntity) zawierający właściwości kolumny 1 i kolumny 2, a typem zwracanym jest: IEnumerable <IGrouping <MyViewEntity, MyEntity >>, a wycinek kodu grupującego to: MyEntityList.GroupBy (myEntity => new MyViewEntity {Column1 = myEntity. Kolumna1, Kolumna2 = myEntity.Column2});
Amir Chatrbahr

467

Ok mam to jako:

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

75
+1 - Dziękujemy za wyczerpujący przykład. Fragmenty drugiej odpowiedzi są zbyt krótkie i pozbawione kontekstu. Wyświetlana jest także funkcja agregująca (w tym przypadku Suma). Bardzo pomocny. Uważam, że użycie funkcji agregującej (tj. MAX, MIN, SUMA itp.) Obok siebie z grupowaniem jest częstym scenariuszem.
barrypicker

Tutaj: stackoverflow.com/questions/14189537/… , jest pokazany dla tabeli danych, gdy grupowanie opiera się na pojedynczej kolumnie, której nazwa jest znana, ale jak można to zrobić, jeśli kolumny oparte na grupowaniu mają być wykonane musi być generowany dynamicznie?
bg

Jest to bardzo pomocne w zrozumieniu koncepcji grupowania i stosowania agregacji.
rajibdotnet

1
Świetny przykład ... właśnie tego szukałem. Potrzebowałem nawet agregatu, więc była to idealna odpowiedź, mimo że szukałem lambdy, mam dość, aby rozwiązać moje potrzeby.
Kevbo,

153

W przypadku grupowania według wielu kolumn spróbuj tego zamiast ...

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

W ten sam sposób możesz dodać kolumnę 3, kolumnę 4 itd.


4
To było bardzo pomocne i powinno uzyskać o wiele więcej pozytywnych opinii! Resultzawiera wszystkie zestawy danych powiązane ze wszystkimi kolumnami. Wielkie dzięki!
j00hi

1
Uwaga: Musiałem użyć .AsEnumerable () zamiast ToList ()
GMan

1
Wspaniale, dzięki za to. Oto mój przykład. Zauważ, że GetFees zwraca IQueryable <Opłata> RegistryAccountDA.GetFees (registerAccountId, fromDate, toDate) .GroupBy (x => nowy {x.AccountId, x.FeeName}, (klucz, grupa) => nowy {AccountId = klucz.konto , FeeName = key.FeeName, AppliedFee = group.Sum (x => x.AppliedFee) ?? 0M}). ToList ();
Craig B,

Czy możliwe jest uzyskanie innych kolumn z tego zapytania, które nie zostały zgrupowane? Jeśli istnieje tablica obiektów, chciałbym pogrupować ten obiekt według dwóch kolumn, ale uzyskać wszystkie właściwości od obiektu, a nie tylko te dwie kolumny.
FrenkyB


19

C # 7.1 lub nowszy przy użyciu Tuplesi Inferred tuple element names(obecnie działa tylko z linq to objectsi nie jest obsługiwany, gdy wymagane są drzewa wyrażeń, np someIQueryable.GroupBy(...). Problem z 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 lub wyższy przy użyciu 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) });

18

Możesz także użyć krotki <> do silnie pogrupowanego grupowania.

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

4
Uwaga: Utworzenie nowej Tuple nie jest obsługiwane w Linq To Entities
foolmoron

8

Chociaż pytanie to dotyczy właściwości grupowania według klas, jeśli chcesz grupować wiele kolumn względem obiektu ADO (takiego jak DataTable), musisz przypisać swoje „nowe” elementy do zmiennych:

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

1
Nie mogłem wykonać zapytania Linq „grupa c według nowego {c.Field <String> („ Title ”), c.Field <String> („ CIF ”)}”, a ty zaoszczędziłeś mi dużo czasu !! ostatnie zapytanie brzmiało: „grupa c według nowego {titulo = c.Field <String> („ Title ”), cif = c.Field <String> („ CIF ”)}”
netadictos


4
.GroupBy(x => x.Column1 + " " + x.Column2)

W połączeniu z Linq.Enumerable.Aggregate()tym nawet pozwala na grupowanie przez szereg dynamicznych właściwości: propertyValues.Aggregate((current, next) => current + " " + next).
Kai Hartmann

3
To jest lepsza odpowiedź, niż ktokolwiek przyznaje. Problemem może być wystąpienie kombinacji, w których kolumna 1 dodana do kolumny 2 byłaby taka sama w sytuacjach, w których kolumna 1 różni się („ab” „cde” pasowałby do „abc” „de”). To powiedziawszy, jest to świetne rozwiązanie, jeśli nie możesz użyć typu dynamicznego, ponieważ konstruujesz lambda po grupie w osobnych wyrażeniach.
Brandon Barkley,

3
„ab” „cde” nie powinno tak naprawdę pasować do „abc” „de”, stąd puste miejsce pomiędzy nimi.
Kai Hartmann

1
co z „abc de” „” i „abc” „de”?
AlbertK

@AlbertK, które chyba nie będzie działać.
Kai Hartmann



1

Należy zauważyć, że musisz wysłać obiekt dla wyrażeń Lambda i nie możesz użyć instancji dla klasy.

Przykład:

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

    public string Prop2 { get; set; }
}

To się skompiluje, ale wygeneruje jeden klucz na cykl .

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

Jeśli nie chcesz nazwać kluczowych właściwości, a następnie je odzyskać, możesz to zrobić w ten sposób. Spowoduje to GroupBypoprawnie i zapewni kluczowe właściwości.

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

0

W przypadku VB i anonimowego / lambda :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.