C# LINQ ile Koleksiyon Sorgulama: Where'den Join'e Uygulamalı Rehber
Beceri rehberi

C# LINQ ile Koleksiyon Sorgulama: Where'den Join'e Uygulamalı Rehber

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 4 dk okuma

LINQ, koleksiyonlardaki veriyi döngü yazmadan filtrelemenizi, dönüştürmenizi, sıralamanızı, gruplamanızı ve birleştirmenizi sağlar. Sayfada temel operatörler ortak bir sipariş örneğiyle gösterilir; ertelenmiş çalıştırma ve en sık yapılan hatalar ayrıca ele alınır.

Bir bakışta

  1. Her LINQ sorgusu üç parçadan oluşur: veri kaynağı, sorgu ve çalıştırma. Sorguyu tanımlamak veriyi getirmez; veri sorgu dolaşıldığında okunur.
  2. Where süzer, Select dönüştürür, OrderBy sıralar. Üçü zincirlenerek birçok döngünün yerini tutar.
  3. GroupBy anahtara göre gruplar, Join iki koleksiyonu ortak alanla eşler. Gruplama sonucunda her öğe, bir anahtar ve ona bağlı elemanlar taşır.
  4. Ertelenmiş çalıştırma, aynı sorguyu her dolaşmada yeniden çalıştırır. Sonucun sabitlenmesi gerekiyorsa ToList ya da ToArray ile bir kez çalıştırın.
  5. Sorguyu yazma biçimi ikiye ayrılır: sorgu sözdizimi ve yöntem sözdizimi. İkisi aynı sonucu verir; zincirleme yöntem biçimi Take ve ToList gibi operatörlerle doğal uyum sağlar.

LINQ sorgusu üç parçadır

Microsoft belgelerine göre her LINQ işlemi aynı üç adımdan geçer: veri kaynağını almak, sorguyu oluşturmak ve sorguyu çalıştırmak. Veri kaynağı, IEnumerable<T> arayüzünü destekleyen herhangi bir nesnedir: dizi, List, Dictionary ya da veritabanı bağlayıcısının döndürdüğü bir sorgu nesnesi. Sorgu, hangi verinin hangi biçimde isteneceğini tanımlar. Çalıştırma ise sonuçlar bir foreach döngüsüyle dolaşıldığında gerçekleşir.

Bu ayrım, LINQ'u anlamanın anahtarıdır. Sorgu değişkeni veri taşımaz, yalnız sorgunun nasıl çalışacağını tutar. Bu yüzden aşağıdaki örneklerin hepsi için ortak bir siparis listesi düşünün; her sipariş bir müşteri, bir durum ve bir tutar alanı taşısın.

Şekil 1

Sorgu tanımlamak veri getirmez, veri dolaşırken gelir

LINQ sorgusunun parçaları

  1. Veri kaynağıIEnumerable<T> destekleyen koleksiyon.
  2. SorguSüzme, sıralama, gruplama ve biçimlendirme tanımı.
  3. Çalıştırmaforeach ile dolaşma ya da ToList gibi bir çağrı.
Kaynak: Microsoft Learn: Introduction to LINQ QueriesMindset Institute

Where, Select ve OrderBy ile ilk sorgu

Belirli bir tutarın üzerindeki onaylı siparişlerin müşteri adlarını, tutara göre azalan sırayla istediğinizi varsayın. Yöntem sözdizimiyle bu tek bir zincirdir:

var sonuc = siparisler .Where(s => s.Durum == "Onaylandi" && s.Tutar > esik) .OrderByDescending(s => s.Tutar) .Select(s => s.Musteri);

Where, koşulu sağlayan öğeleri bırakır. OrderByDescending sıralar; ikinci bir ölçüt gerekirse ThenBy eklenir. Select ise her öğeyi istenen biçime çevirir; yalnız bir alan seçebileceğiniz gibi birkaç alanı taşıyan bir anonim tür de üretebilirsiniz. Aynı sorgu sorgu sözdizimiyle from, where, orderby ve select ifadeleriyle de yazılır. Belgeler, bu ifadelerin sırasının SQL'dekinden farklı olduğunu, from ile başlayıp select ile bittiğini belirtir.

Sıra önemlidir: önce Where, sonra OrderBy yazmak, sıralanacak öğe sayısını azaltır. Select'i en sona koymak ise sıralama için gereken alanların yitirilmemesini sağlar.

Select ile yalnız ihtiyaç duyduğunuz alanları almak, sonraki adımlarda taşınan veriyi küçültür ve niyetinizi okuyana açıkça gösterir. Örneğin müşteri adı ve tutarı birlikte isteyen bir sorgu, bir anonim tür üretir ve sonuçta bu iki alanı taşıyan nesneler dolaşılır.

GroupBy ile müşteri bazında özet

Her müşterinin toplam sipariş tutarını görmek istiyorsunuz. GroupBy, öğeleri bir anahtara göre kümeler; her grubun Key alanı anahtarı, kendisi de o gruba ait öğeleri taşır. Özeti çıkarmak için gruplama sonrasında Select ile Sum gibi bir toplama kullanılır:

var ozet = siparisler .GroupBy(s => s.Musteri) .Select(g => new { Musteri = g.Key, Toplam = g.Sum(s => s.Tutar) });

Sum, Count, Average, Min ve Max gibi operatörler tek bir değer döndürür ve anında çalışır. GroupBy ise sıralama gibi, sonuç üretmeden önce tüm kaynak veriyi okuması gereken operatörlerdendir; büyük koleksiyonlarda bu bellek maliyeti demektir.

Sık bir hata, grupladıktan sonra her grubun elemanlarını yeniden başka bir döngüyle dolaşmaktır. Gerekli toplama zaten gruplama zincirinin içinde yapılabilir.

Join ile iki koleksiyonu eşlemek

Siparişler müşteri kimliği taşıyor, müşteri bilgileri ise ayrı bir listede duruyor olsun. Join, iki koleksiyonu ortak bir anahtarla eşler ve her eşleşme için istediğiniz sonucu üretir:

var liste = siparisler.Join( musteriler, s => s.MusteriNo, m => m.No, (s, m) => new { m.Ad, s.Tutar });

Önce dış koleksiyon ve iç koleksiyon, ardından iki anahtar seçici ve en sonda sonucu kuran ifade verilir. Eşleşmesi olmayan siparişler sonuçta yer almaz. Eşleşmesi olmayanları da görmek istiyorsanız GroupJoin ile DefaultIfEmpty birlikte kullanılır. Veritabanıyla çalışırken Join yazmak yerine ilişkili nesnelere doğrudan gitmek bazen daha sadedir; bu, veri erişim katmanının konusudur.

Ertelenmiş çalıştırma: en çok yanıltan kural

Microsoft belgeleri, operatörleri çalıştırma biçimine göre iki gruba ayırır. Where, Select ve Take gibi operatörler ertelenmiştir: sorgu tanımlandığında hiçbir şey olmaz, sonuçlar dolaşıldığında çalışır. Count, Sum, First, ToList ve ToArray gibi operatörler ise çağrıldıkları anda çalışır. OrderBy ve GroupBy de ertelenmiştir ama çalıştıklarında tüm kaynağı okurlar.

Bunun iki sonucu vardır. Birincisi, sorgu değişkeni her dolaşıldığında kaynağın o anki içeriğiyle yeniden çalışır; kaynak arada değiştiyse sonuçlar da değişir. İkincisi, aynı sorguyu birçok kez dolaşmak aynı işi tekrar tekrar yapar. Sonucu bir kez hesaplayıp sabitlemek istiyorsanız ToList ya da ToArray ile çalıştırıp çıktıyı yeni bir değişkene atayın.

Bu tuzak pratikte şöyle görünür: bir sorguyu tanımlar, sonra kaynak listeye yeni siparişler eklersiniz ve sorguyu dolaşırsınız. Yeni siparişler sonuca girer, çünkü sorgu o anda çalışmıştır. Tersine, sonucu ToList ile aldıysanız sonradan eklenen siparişler görünmez. Hangisini istediğinize önceden karar verin. Bir başka sık hata, bir koleksiyonu dolaşırken aynı koleksiyonu değiştirmeye çalışmaktır; sorguyu önce ToList ile sabitlemek bu durumdan kaçınmanın sade yoludur.

Şekil 2

Operatörün türü, ne zaman çalışacağını belirler

Çalıştırma biçimleri

ErtelenmişAnında
Filtre ve dönüştürmeWhere, Select, TakeYok; sorgunun sonuna eklenen bir çağrı gerekir
Sıralama ve gruplamaOrderBy, GroupBy: ertelenmiş ama tüm kaynağı okurYok
Tek değerYokCount, Sum, First, Max
Sonucu sabitlemeYokToList, ToArray
Kaynak: Microsoft Learn: Introduction to LINQ Queries, operatör sınıflandırma tablosuMindset Institute

LINQ'u öğrenirken uygulanacak yol

Önce bir listeyi foreach ve if ile filtrelediğiniz küçük bir uygulama yazın, sonra aynısını Where ile yeniden kurun; farkı kendi gözünüzle görmek en hızlı öğrenme yoludur. Ardından sırasıyla Select ile alan dönüştürme, OrderBy ile sıralama, GroupBy ile özet ve Join ile iki kaynağı birleştirme gelir. Her adımda sonucu ekrana yazdırın ve ertelenmiş çalıştırmanın etkisini görmek için kaynağa sorgudan sonra yeni bir öğe ekleyip sorguyu yeniden dolaşın.

Mindset kataloğunda İleri Düzey C# Programlama programı Koleksiyonlar, LINQ ve veri işleme modülünü içerir; nesne yönelimli tasarım ve asenkron programlama modülleriyle birlikte, LINQ'u gerçek uygulama kodunda kullanmak isteyen C# geliştiricileri için bir sonraki adımdır.

İlgili Mindset programı

İleri Düzey C# Programlama Dili EĞitimi

Sık sorulanlar

Sık sorulan sorular

Sorgu sözdizimi mi yöntem sözdizimi mi kullanmalıyım?

İkisi aynı işi görür; derleyici sorgu ifadesini yöntem çağrılarına çevirir. Birleştirme ve gruplama içeren karmaşık sorgular sorgu sözdiziminde okunaklı olabilir. Take ve ToList gibi operatörler yalnız yöntem biçiminde bulunur. Hangisini seçerseniz seçin ekibinizin tutarlı olması yeterlidir.

ToList ne zaman çağrılmalı?

Sorgu sonucunu birden çok kez kullanacaksanız, kaynağın sonradan değişmesinin sonucu etkilememesini istiyorsanız ya da sorguyu belirli bir noktada çalıştırmak gerekiyorsa. Sonucu yalnız bir kez dolaşacaksanız ToList gereksiz bir kopya ve bellek harcamasıdır.

First ile FirstOrDefault arasındaki fark nedir?

İkisi de ilk öğeyi döndürür. Koleksiyon boşsa First hata fırlatır, FirstOrDefault ise tür için varsayılan değeri verir. Boş sonucun normal bir durum olduğu yerde FirstOrDefault kullanın ve dönen değeri boşluk için kontrol edin.

LINQ döngüden yavaş mıdır?

Fark, yazdığınız koda ve veri boyutuna bağlıdır; okunabilirlik kazancı çoğu zaman dikkate değerdir ama bunu varsaymayın. Sık çalışan bir kod yolunda ya da büyük koleksiyonlarda ölçüm yapmadan karar vermeyin. Bir sorguyu gereksiz yere birçok kez dolaşmak, LINQ'un kendisinden daha büyük bir maliyet doğurur.

Veritabanı sorgularında LINQ aynı mı çalışır?

Sözdizimi benzerdir ama sorgu, bellekteki koleksiyon yerine veri sağlayıcısına çevrilir. Entity Framework gibi bir ORM kullanıyorsanız her ifade veritabanına çevrilebilir olmayabilir. Bu konu veri erişim katmanında ayrıca incelenir.

C# LINQ Sorguları Nasıl Yazılır? Where, GroupBy, Join