Foto oleh Pixabay via Pexels

LINQ (Language Integrated Query) adalah fitur C# yang memungkinkan query terhadap koleksi data ditulis dengan sintaks yang deklaratif dan ekspresif, mirip seperti menulis query SQL namun langsung terintegrasi dalam bahasa C# itu sendiri.

LINQ bekerja terhadap berbagai sumber data yang mengimplementasikan IEnumerable, termasuk array, List, Dictionary, bahkan data dari database melalui Entity Framework, menjadikannya cara yang konsisten untuk melakukan filter, transformasi, dan agregasi data di berbagai konteks.

Artikel ini membahas method LINQ paling sering dipakai seperti Where, Select, OrderBy, dan agregasi seperti Sum dan Count, serta perbedaan antara method syntax dan query syntax yang keduanya valid namun memiliki gaya penulisan berbeda.

Where untuk Memfilter Data

Where menyaring elemen berdasarkan kondisi lambda

List<int> angka = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 }; var angkaGenap = angka.Where(a => a % 2 == 0).ToList(); Console.WriteLine(string.Join(", ", angkaGenap)); -- Expected: 2, 4, 6, 8
Method Where menerima lambda expression yang mengembalikan bool untuk setiap elemen, hanya elemen yang menghasilkan true yang akan masuk ke hasil akhir. Method ini bersifat lazy (deferred execution), artinya query sebenarnya baru dieksekusi ketika hasilnya benar-benar diiterasi atau dikonversi, misalnya dengan ToList().

Select untuk Transformasi Data

Select mengubah setiap elemen menjadi bentuk baru

List<Produk> produk = GetDaftarProduk(); var namaProduk = produk.Select(p => p.Nama).ToList(); var ringkasan = produk.Select(p => new { p.Nama, HargaDiskon = p.Harga * 0.9m }).ToList();
Method Select mentransformasi setiap elemen koleksi menjadi bentuk baru sesuai lambda yang diberikan, bisa berupa nilai tunggal seperti nama produk saja, atau object anonim baru yang menggabungkan beberapa property sekaligus dengan transformasi tambahan seperti perhitungan diskon.

OrderBy dan Agregasi dengan Sum, Count, Average

Kombinasi OrderBy dan fungsi agregat LINQ

var produkTermahal = produk.OrderByDescending(p => p.Harga).First(); int totalProduk = produk.Count(); decimal totalNilai = produk.Sum(p => p.Harga); decimal rataRata = produk.Average(p => p.Harga);
OrderBy dan OrderByDescending mengurutkan koleksi berdasarkan kriteria tertentu, sementara Sum, Count, dan Average menghitung nilai agregat dari koleksi tersebut, semuanya dengan sintaks yang konsisten dan bisa dirangkai (chaining) satu sama lain untuk membentuk query yang lebih kompleks dalam satu baris.

Method Syntax vs Query Syntax

Dua gaya penulisan LINQ yang setara hasilnya

// Method syntax var hasil1 = produk.Where(p => p.Harga > 1000000).OrderBy(p => p.Nama).ToList(); // Query syntax (gaya mirip SQL) var hasil2 = from p in produk where p.Harga > 1000000 orderby p.Nama select p;
Query syntax menawarkan gaya penulisan yang mirip SQL dan kadang lebih mudah dibaca untuk query yang kompleks dengan banyak join, sementara method syntax lebih ringkas untuk query sederhana dan lebih mudah dirangkai dengan method LINQ lain yang tidak memiliki padanan kata kunci di query syntax, seperti Skip atau Take.

Deferred Execution pada LINQ dan Dampaknya terhadap Hasil Query

LINQ Where dieksekusi secara deferred, bukan saat dideklarasikan

List<int> angka = new List<int> { 1, 2, 3 }; var query = angka.Where(x => x > 1); angka.Add(4); foreach (var a in query) Console.WriteLine(a); // Output: 2, 3, 4 (ikut termasuk data yang ditambahkan setelah query dibuat)

Sebagian besar operator LINQ seperti Where, Select, dan OrderBy tidak langsung dieksekusi pada saat baris kode query dituliskan, melainkan baru benar-benar dijalankan ketika hasilnya diiterasi, misalnya melalui foreach atau dipaksa dengan method seperti ToList() dan ToArray(). Perilaku ini disebut deferred execution, dan pada contoh di atas menyebabkan angka yang ditambahkan ke list setelah query dideklarasikan tetap ikut masuk ke dalam hasil akhir karena query baru dievaluasi saat foreach berjalan.

Operator seperti Count(), Sum(), First(), atau ToList() justru bersifat immediate execution, artinya langsung mengeksekusi seluruh query pada saat dipanggil dan mengembalikan hasil akhirnya, bukan objek query yang masih bisa berubah. Memahami perbedaan ini penting untuk menghindari bug yang sulit dilacak, terutama ketika sebuah query LINQ disimpan dalam variabel lalu dipakai berulang kali di tempat yang berbeda sementara sumber datanya ikut berubah di antara pemakaian tersebut.

Jika hasil query dimaksudkan untuk dipakai sebagai snapshot data pada satu titik waktu tertentu dan tidak boleh berubah meskipun sumber datanya berubah setelahnya, solusinya adalah memaksa eksekusi segera dengan memanggil ToList() atau ToArray() tepat setelah query dideklarasikan, sehingga hasilnya disalin ke koleksi baru yang independen dari sumber data aslinya.

GroupBy dan OrderBy untuk Agregasi dan Pengurutan Data dengan LINQ

GroupBy mengelompokkan data, OrderByDescending mengurutkan dari nilai terbesar

var transaksi = new List<(string Kategori, decimal Nominal)> { ("Makanan", 50000), ("Transport", 20000), ("Makanan", 75000), ("Transport", 15000) }; var rekap = transaksi .GroupBy(t => t.Kategori) .Select(g => new { Kategori = g.Key, Total = g.Sum(x => x.Nominal) }) .OrderByDescending(r => r.Total); foreach (var r in rekap) Console.WriteLine($"{r.Kategori}: {r.Total}"); // Output: // Makanan: 125000 // Transport: 35000

Operator GroupBy pada LINQ mengelompokkan elemen koleksi berdasarkan key tertentu yang ditentukan melalui lambda expression, menghasilkan koleksi grup di mana setiap grup memiliki property Key sebagai nilai pengelompokan dan dapat diiterasi untuk mengakses seluruh elemen yang termasuk dalam grup tersebut. Pada contoh di atas, transaksi dikelompokkan berdasarkan Kategori, lalu setiap grup dijumlahkan nominalnya menggunakan Sum untuk menghasilkan rekap total per kategori.

OrderBy dan OrderByDescending dipakai untuk mengurutkan hasil query berdasarkan satu atau lebih kriteria, dengan OrderByDescending pada contoh di atas mengurutkan rekap dari total nominal terbesar ke terkecil. Jika dibutuhkan pengurutan berdasarkan lebih dari satu kriteria, method ThenBy atau ThenByDescending dapat dirangkai setelah OrderBy untuk menentukan kriteria pengurutan kedua ketika nilai kriteria pertama sama persis antar elemen.

Kombinasi GroupBy, Select, dan OrderBy seperti pada contoh di atas sangat umum dipakai untuk kebutuhan pelaporan dan dashboard, menggantikan query agregasi SQL seperti GROUP BY dan ORDER BY namun ditulis langsung dalam kode C# menggunakan koleksi in-memory. Perlu diperhatikan bahwa GroupBy pada LINQ to Objects memuat seluruh data ke memori terlebih dahulu, sehingga untuk dataset yang sangat besar, melakukan agregasi langsung pada level database query umumnya jauh lebih efisien. Ketika LINQ dipakai di atas Entity Framework misalnya, GroupBy dan OrderBy akan diterjemahkan menjadi perintah SQL yang dijalankan langsung oleh database, sehingga hanya hasil akhirnya saja yang dikirim kembali ke aplikasi, bukan seluruh baris data mentah.

Query Syntax vs Method Syntax pada LINQ

var angka = new List<int> { 5, 2, 8, 1, 9, 3 }; // Query syntax var hasilQuery = from n in angka where n > 3 orderby n select n; // Method syntax var hasilMethod = angka.Where(n => n > 3).OrderBy(n => n); Console.WriteLine(string.Join(",", hasilQuery)); Console.WriteLine(string.Join(",", hasilMethod)); // Output: // 5,8,9 // 5,8,9

LINQ menyediakan dua gaya penulisan yang setara secara fungsional: query syntax yang mirip perintah SQL dengan kata kunci from, where, orderby, dan select, serta method syntax yang memanggil method ekstensi seperti Where() dan OrderBy() secara berantai menggunakan lambda expression. Keduanya dikompilasi menjadi kode yang pada dasarnya identik di belakang layar, sehingga tidak ada perbedaan performa yang berarti antara memilih salah satunya, murni soal gaya penulisan dan preferensi tim.

Query syntax cenderung lebih mudah dibaca untuk query dengan banyak klausa seperti join atau group yang kompleks karena strukturnya menyerupai bahasa query yang sudah dikenal, sementara method syntax lebih fleksibel karena bisa mengakses seluruh method ekstensi LINQ, termasuk yang tidak memiliki padanan kata kunci query seperti Count() atau FirstOrDefault(). Pemilihan gaya biasanya mengikuti konvensi tim atau kenyamanan pribadi, dan keduanya bahkan bisa dicampur dalam satu project yang sama.

Kesalahan Umum yang Sering Terjadi

Memanggil ToList() Berulang Kali pada Query yang Sama

Memanggil ToList() atau mengiterasi query LINQ yang sama berkali-kali akan mengeksekusi ulang seluruh logika query setiap saat karena sifatnya yang deferred execution, sebaiknya hasilnya disimpan sekali ke variabel jika akan dipakai berulang.

Lupa Query LINQ Bersifat Lazy, Bukan Langsung Dieksekusi

Mengubah koleksi sumber data setelah query LINQ didefinisikan namun sebelum diiterasi bisa menghasilkan hasil yang tidak terduga, karena query baru benar-benar dieksekusi saat iterasi terjadi, bukan saat baris query ditulis.

Menggunakan First() Tanpa Memeriksa Kemungkinan Koleksi Kosong

Method First() akan melempar InvalidOperationException jika koleksi kosong; FirstOrDefault() adalah alternatif yang lebih aman karena mengembalikan nilai default (biasanya null) alih-alih melempar exception.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Method syntax lebih fleksibel dan mudah dirangkai dengan method LINQ lanjutan, menjadikannya pilihan yang lebih umum dipakai di kalangan developer C# untuk kebanyakan kasus sehari-hari.

Query syntax memberikan keterbacaan yang lebih baik untuk query kompleks dengan banyak join antar beberapa koleksi sumber data, mirip dengan menulis query SQL, namun tidak semua method LINQ memiliki padanan dalam query syntax, sehingga kadang perlu dikombinasikan dengan method syntax.

Ringkasan

LINQ memberikan cara yang deklaratif dan ekspresif untuk melakukan filter, transformasi, dan agregasi data terhadap berbagai sumber koleksi. Pahami sifat lazy execution-nya dan pilih antara method syntax atau query syntax sesuai kompleksitas query dan preferensi keterbacaan tim, karena keduanya menghasilkan hasil akhir yang identik.

Seri Belajar C# Fundamental:

← Sebelumnya: Collection Generic List Dictionary dan HashSet

→ Selanjutnya: Delegate Event dan Action Func di C#