LINQ, C#'ın en güçlü özelliklerinden biri. Ama ne zaman çalışır, kaç kez çalışır ve nerede çalışır sorularını yanıtlayamazsanız farkında olmadan ciddi performans hataları yapabilirsiniz.
Deferred Execution: Sorgu Tanımlama ≠ Sorgu Çalıştırma
LINQ sorgularının büyük çoğunluğu tanımlandıkları anda çalışmaz. Sonuç talep edildiğinde çalışır. Buna ertelenmiş yürütme (deferred execution) denir.
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Bu satır SORGU TANIMLAR, çalıştırmaz
var query = numbers.Where(n => n % 2 == 0);
Console.WriteLine("Sorgu tanımlandı");
// Veriyi listeye ekle
numbers.Add(6);
numbers.Add(7);
numbers.Add(8);
// Sorgu BURADA çalışır — 6 ve 8 de dahil!
foreach (var n in query)
Console.Write(n + " "); // 2 4 6 8
// Immediate execution operatörleri: ToList, ToArray, Count, First, Sum...
var result = numbers.Where(n => n % 2 == 0).ToList(); // Hemen çalışır
Deferred Execution Tuzağı
// Kötü: Sorgu her foreach'te yeniden çalışır
var expensiveQuery = db.Products
.Where(p => p.IsActive)
.OrderByDescending(p => p.Price);
// Birinci döngü: SQL çalışır
foreach (var p in expensiveQuery) Console.WriteLine(p.Name);
// İkinci döngü: SQL TEKRAR çalışır
var count = expensiveQuery.Count(); // Başka bir SQL
// İyi: Bir kez materialize et
var products = expensiveQuery.ToList();
foreach (var p in products) Console.WriteLine(p.Name);
var count = products.Count; // Bellek — SQL yok
IEnumerable vs IQueryable: Nerede Çalışır?
Bu iki interface arasındaki fark, sorgunun nerede yürütüldüğünü belirler.
// IEnumerable: Uygulama belleğinde çalışır (LINQ to Objects)
IEnumerable<Product> InMemoryFilter(IEnumerable<Product> products, decimal minPrice)
{
return products.Where(p => p.Price > minPrice);
// Tüm products belleğe çekilir, sonra filtre uygulanır
}
// IQueryable: Veritabanında çalışır (LINQ to SQL/EF)
IQueryable<Product> DatabaseFilter(IQueryable<Product> products, decimal minPrice)
{
return products.Where(p => p.Price > minPrice);
// SQL: SELECT * FROM Products WHERE Price > @minPrice
// Sadece eşleşen kayıtlar gelir
}
Bu Fark Pratikte Neden Önemli?
// Tehlikeli: IEnumerable parametresi alan metod
public IEnumerable<Product> GetActive(IEnumerable<Product> source)
=> source.Where(p => p.IsActive);
// EF DbSet IQueryable döner, IEnumerable'a cast edilirse:
var products = _db.Products; // IQueryable
GetActive(products) // IEnumerable parametresi kabul eder
// Ama TÜM Products tablosu belleğe çekilir!
// Doğru: IQueryable döndür
public IQueryable<Product> GetActive(IQueryable<Product> source)
=> source.Where(p => p.IsActive);
// SQL: SELECT * FROM Products WHERE IsActive = 1
Expression Trees: IQueryable'ın Motoru
IEnumerable<T>'ın delegate aldığı yerde, IQueryable<T> expression tree alır. Bu sayede LINQ sorgusu SQL'e çevrilebilir.
// Bu iki tanım çok farklı şeyler ifade eder:
// 1. Func: Derlenmiş kod — çalıştırılabilir ama incelenemiyor
Func<Product, bool> funcFilter = p => p.Price > 100;
// 2. Expression: Ağaç yapısı — incelenebilir ve çevrilebilir
Expression<Func<Product, bool>> exprFilter = p => p.Price > 100;
// exprFilter'ın yapısını görelim
var binary = (BinaryExpression)exprFilter.Body;
Console.WriteLine(binary.Left); // p.Price
Console.WriteLine(binary.NodeType); // GreaterThan
Console.WriteLine(binary.Right); // 100
// EF Core bu ağacı alır ve şuna çevirir:
// WHERE [p].[Price] > 100.0
Dinamik Sorgu Oluşturma
// Çalışma zamanında filtre oluşturma
public static IQueryable<T> WhereIf<T>(
this IQueryable<T> query,
bool condition,
Expression<Func<T, bool>> predicate)
=> condition ? query.Where(predicate) : query;
// Kullanım
var products = db.Products
.WhereIf(!string.IsNullOrEmpty(search),
p => p.Name.Contains(search!))
.WhereIf(minPrice.HasValue,
p => p.Price >= minPrice!.Value)
.WhereIf(categoryId.HasValue,
p => p.CategoryId == categoryId!.Value);
// Gelişmiş: Property'ye göre dinamik sıralama
public static IQueryable<T> OrderByProperty<T>(
this IQueryable<T> source, string propertyName, bool ascending = true)
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, propertyName);
var lambda = Expression.Lambda(prop, param);
var method = ascending ? "OrderBy" : "OrderByDescending";
var expr = Expression.Call(
typeof(Queryable), method,
[typeof(T), prop.Type],
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(expr);
}
// Kullanım
var sorted = db.Products.OrderByProperty("Price", ascending: false);
Sık Yapılan LINQ Hataları
// Hata 1: Count() yerine Any() kullanmamak
if (orders.Count() > 0) // Tüm sayıyı çeker
if (orders.Any()) // İlk elemanı bulunca durur
// Hata 2: FirstOrDefault sonrası null kontrolü unutmak
var user = users.FirstOrDefault(u => u.Email == email);
Console.WriteLine(user.Name); // NullReferenceException!
var user = users.FirstOrDefault(u => u.Email == email)
?? throw new NotFoundException(email);
// Hata 3: Select içinde metod çağrısı (IQueryable'da desteklenmeyebilir)
var names = db.Users.Select(u => u.GetFullName()); // EF çeviremez!
var names = db.Users.Select(u => u.FirstName + " " + u.LastName); // OK
// Hata 4: OrderBy sonrası Where uygulamak
var wrong = items.OrderBy(x => x.Name).Where(x => x.IsActive); // Gereksiz sort
var correct = items.Where(x => x.IsActive).OrderBy(x => x.Name); // Önce filtre
LINQ'un gücü, doğru kullanıldığında hem okunabilir hem verimli kod yazmanızı sağlamasından geliyor. Deferred execution'ı anlamak, IEnumerable ile IQueryable farkını bilmek ve expression tree mekanizmasına hakim olmak, bu gücü gerçekten kullanabilmenin temeli.