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

Odpowiedzi:

1212

Użyj anonimowego typu.

Na przykład

group x by new { x.Column1, x.Column2 }
leppie
źródło
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 })
Mo0gles
źródło
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();
Sreedhar
źródło
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.

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

Od wersji C # 7 można także używać krotek wartości:

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

lub

.GroupBy(x => (x.Column1, x.Column2))
Nathan Tregillus
źródło
2
Myślę, że brakuje ci dodatkowej) na końcu. Nie zamykasz ()
StuiterSlurf
Dodałem to.
Nathan Tregillus
2
.GroupBy (x => new {x.Column1, x.Column2})
Zahidul Islam Jamy
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) });
AlbertK
źródło
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),
}
Jay Bienvenu
źródło
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);
Chris Smith
źródło
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
var Results= query.GroupBy(f => new { /* add members here */  });
Arindam
źródło
7
Nic nie dodaje do poprzednich odpowiedzi.
Mike Fuchs,
4
.GroupBy(x => x.Column1 + " " + x.Column2)
Kai Hartmann
źródło
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
3

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

Rozpalmy
źródło
3
Rozważ dodanie wyjaśnienia, w jaki sposób ten kod rozwiązuje problem.
Rosário Pereira Fernandes
2

zgrupuj x według nowego {x.Col, x.Col}

Jan
źródło
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;
}
Ogglas
źródło
0

W przypadku VB i anonimowego / lambda :

query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })
Dani
źródło