Laporan per kategori menuntut data dikelompokkan lebih dulu: berapa banyak produk tiap kategori dan berapa harga rata-ratanya. Operator GroupBy membagi urutan menjadi sejumlah grup, satu grup untuk satu nilai kunci, lalu setiap grup dapat diringkas dengan operator agregasi.

Mengelompokkan Berdasarkan Kunci #

Contoh berikut menghitung isi dan harga rata-rata tiap kategori:

csharp
record Produk(string Nama, string Kategori, int Harga, int Stok);

List<Produk> produk = new()
{
    new Produk("Kopi Gayo", "Minuman", 25000, 40),
    new Produk("Teh Melati", "Minuman", 15000, 60),
    new Produk("Roti Bakar", "Makanan", 12000, 25),
    new Produk("Susu UHT", "Minuman", 20000, 35),
    new Produk("Keripik Singkong", "Makanan", 10000, 80)
};

var perKategori = produk.GroupBy(p => p.Kategori);

foreach (var g in perKategori)
{
    string nama = string.Join(", ", g.Select(p => p.Nama));
    Console.WriteLine($"{g.Key}: {g.Count()} produk ({nama})");
    Console.WriteLine($"Rata-rata harga: {g.Average(p => p.Harga)}");
}

Output yang Dihasilkan:

text
Minuman: 3 produk (Kopi Gayo, Teh Melati, Susu UHT)
Rata-rata harga: 20000
Makanan: 2 produk (Roti Bakar, Keripik Singkong)
Rata-rata harga: 11000

Mari kita bedah baris demi baris:

  1. produk.GroupBy(p => p.Kategori): kunci grup adalah nilai Kategori; setiap grup bertipe IGrouping<string, Produk>.
  2. g.Key: property Key membaca nilai kunci grup, misalnya Minuman.
  3. g.Select(p => p.Nama): grup itu sendiri adalah urutan elemen, sehingga operator LINQ lain tetap dapat dipakai di dalamnya.
  4. g.Count() dan g.Average(...): ringkasan dihitung per grup, bukan atas seluruh data sekaligus.
  5. urutan grup: grup muncul sesuai kemunculan pertama kuncinya di sumber, sehingga Minuman tampil lebih dulu.

Bentuk Query Expression #

Dalam query expression, pengelompokan ditulis group p by p.Kategori into g, lalu klausa select membentuk hasil per grup. Identifikasi grup tetap lewat g.Key, dan isi grup tetap dapat disaring atau diringkas dengan operator lain.

GroupBy juga menerima element selector sehingga isi grup langsung dipangkas: produk.GroupBy(p => p.Kategori, p => p.Nama) menghasilkan grup berisi nama saja, bukan object Produk utuh. Setelah grup terbentuk, urutan grup dapat ditata dengan OrderBy(g => g.Key) karena urutan grup bawaan mengikuti kemunculan pertama, bukan urutan alfabetis kunci.

Grup yang sudah jadi juga dapat disaring dengan Where yang diletakkan setelah GroupBy, misalnya produk.GroupBy(p => p.Kategori).Where(g => g.Count() >= 2) untuk hanya mengambil kategori dengan minimal dua produk. Letak syarat ini menentukan maknanya: syarat sebelum GroupBy menguji produk satu per satu, sedangkan syarat setelahnya menguji grup utuh beserta ringkasannya.

Bab Aggregation memperdalam operator agregasi seperti Count dan Average, termasuk overload selector yang dipakai pada contoh di atas.