LINQ Where() İki Milyon Siparişte Dört Milyon Kontrol Yaptı
Bir raporlama endpoint'i yavaşlamış, kod incelemesine gittim. Ortasında tamamen zararsız görünen üç satırlık bir metod vardı: siparişleri sayıp, kaç tanesinin faturalanabilir olduğunu logla, toplamını hesapla.
```csharp
public static decimal Summarize(IEnumerable billable)
{
int n = billable.Count();
decimal total = 0m;
foreach (var o in billable)
total += o.Amount;
log.Information("{Count} billable orders", n);
return total;
}
```
Problemi çağrı tarafında buldum:
```csharp
Summarize(orders.Where(IsBillable));
```
Her şey burada yatıyordu. `Where()` bir filtreleme işlemi yapmıyor, onu daha sonra yapacağını söyleyen bir nesne döndürüyor. LINQ'de buna lazy evaluation deniyor. Ama `Summarize` bu nesneyi iki kez yürütüyor: `Count()` için bir kez, `foreach` döngüsü için bir kez. Her yürütüş, her elemana `IsBillable` kontrolünü baştan sona uyguluyor.
İki milyon siparişle bu, dört milyon kontrol demek. Filtrenin bir kez yapılacağını düşünmüştüm, ama LINQ lazy evaluation'ı nedeniyle her çağrı tam tekrar işlemi tetikliyordu.
Çözüm basit: `Where()` sonucunu `ToList()` ya da `ToArray()` ile derle:
```csharp
Summarize(orders.Where(IsBillable).ToList());
```
Böyle filtreleme bir kez çalışır, sonuç bellekte tutulur. Ya da metodu içinde derle:
```csharp
public static decimal Summarize(IEnumerable billable)
{
var list = billable.ToList();
int n = list.Count;
decimal total = 0m;
foreach (var o in list)
total += o.Amount;
log.Information("{Count} billable orders", n);
return total;
}
```
Kodu gözden geçirirken `IEnumerable` parametresinin üstünde durmalısın. Lazy evaluation LINQ'nin gücü ama aynı zamanda tuzağı. Eğer `IEnumerable`'ı birden fazla yerde yürütüyorsan, bilinçli bir seçim yap, derle ya da akışını tek sefere indir.
0 yorum