IEnumerable vs IQueryable w EF Core

IEnumerable vs IQueryable w EF Core – wydajność i pułapki

IEnumerable vs IQueryable w EF Core – wydajność i pułapki

Masz działający kod w EF Core, wrzucasz go na produkcję… i nagle aplikacja zaczyna zużywać ogromne ilości RAM, a baza danych dostaje zadyszki?

W wielu przypadkach przyczyną jest jedna, pozornie niewinna decyzja:
👉 użycie IEnumerable zamiast IQueryable.

W tym artykule pokażę Ci realny problem produkcyjny, wyjaśnię jak działa EF Core pod maską i pokażę, jak pisać wydajny kod zgodny z Clean Architecture.


🔍 IEnumerable vs IQueryable – o co naprawdę chodzi?

Na pierwszy rzut oka oba interfejsy wyglądają identycznie:

  • oba wspierają LINQ
  • oba działają z foreach
  • oba kompilują się poprawnie

👉 Ale różnica jest fundamentalna: gdzie wykonywane jest zapytanie.


📌 IEnumerable – przetwarzanie w pamięci (LINQ to Objects)

var orders = repository.GetAllOrdersAsEnumerable()
    .Where(o => o.TotalAmount > 1000)
    .ToList();

Co się tutaj dzieje?

  • EF Core wykonuje:
SELECT * FROM Orders
  • dane trafiają do pamięci (RAM)
  • dopiero potem działa .Where()

❌ Problem

  • pobierasz wszystkie rekordy
  • filtrujesz je lokalnie
  • marnujesz:
    • RAM
    • CPU
    • sieć

👉 To klasyczny antywzorzec: Client-Side Evaluation


📌 IQueryable – przetwarzanie w bazie (LINQ to Entities)

var orders = repository.GetAllOrdersAsQueryable()
    .Where(o => o.TotalAmount > 1000)
    .ToList();

Co robi EF Core?

SELECT * FROM Orders WHERE TotalAmount > 1000

👉 Filtr odbywa się w bazie danych.


✅ Efekt

  • mniej danych w sieci
  • mniej RAM
  • lepsza wydajność
  • skalowalność

⚙️ Jak to działa pod maską?

Kluczowa różnica:

TypMechanizm
IEnumerableDelegaty (Func<T, bool>)
IQueryableExpression Trees

🧠 Expression Trees – dlaczego to działa?

Expression<Func<Order, bool>> expr = o => o.TotalAmount > 1000;

EF Core:

  1. analizuje drzewo wyrażeń
  2. tłumaczy je na SQL
  3. wysyła do bazy

👉 To nie jest wykonywanie kodu — to opis zapytania


🔥 Najczęstsze pułapki (realne case’y produkcyjne)

❌ 1. Przedwczesne .ToList()

var orders = context.Orders.ToList()
     .Where(o => o.TotalAmount > 1000);

👉 Zabijasz optymalizację SQL


❌ 2. .AsEnumerable() w złym miejscu

var orders = context.Orders
    .AsEnumerable()
    .Where(o => o.TotalAmount > 1000);

👉 Przełączasz się na LINQ to Objects


❌ 3. Zbyt skomplikowane metody

.Where(o => CustomBusinessLogic(o))

👉 EF Core nie przetłumaczy tego na SQL
👉 runtime exception


🧱 Problem architektoniczny – Leaky Abstraction

Skoro IQueryable jest takie dobre…

👉 dlaczego nie zwracać go zawsze z repozytorium?


❌ Problem

public IQueryable<Order> GetOrders()
  • warstwa wyżej buduje zapytania
  • zna szczegóły EF Core
  • łamiesz hermetyzację

🔥 Konsekwencje

  • tight coupling
  • trudne debugowanie
  • błędy runtime zamiast compile-time

✅ Rozwiązanie: Specification Pattern

Zamiast:

repository.GetOrders().Where(...)

robisz:

repository.ListAsync(new ExpensiveOrdersSpecification());

📌 Przykład

public class ExpensiveOrdersSpecification : Specification<Order>
{
    public ExpensiveOrdersSpecification()
    {
        Query.Where(o => o.TotalAmount > 1000);
    }
}

✅ Zalety

  • brak wycieku IQueryable
  • pełna kontrola zapytań
  • zgodność z Clean Architecture
  • reużywalność

📊 Checklist – jak pisać wydajny kod w EF Core

✅ Filtruj na IQueryable
✅ Używaj .Select() do projekcji
✅ Loguj SQL (LogTo)
✅ Unikaj .ToList() za wcześnie
✅ Nie używaj .AsEnumerable() bez potrzeby
✅ Testuj na dużych danych


⚡ Szybkie podsumowanie

  • IEnumerable = przetwarzanie w RAM
  • IQueryable = przetwarzanie w bazie
  • kluczowy jest moment wykonania zapytania
  • zła decyzja = ogromne problemy wydajnościowe
  • IQueryable rozwiązuje wydajność, ale wprowadza problem architektoniczny
  • rozwiązaniem jest Specification Pattern

🔗 Zobacz też


📢 Call To Action

Jeśli ten artykuł pomógł Ci zrozumieć różnicę między IEnumerable a IQueryable:

👉 zostaw komentarz – jakie problemy wydajnościowe spotkałeś w EF Core?
👉 udostępnij artykuł innym devom

Zobacz także — powiązane artykuły

👉 Tworzenie klas i obiektów w C# — kompletny przewodnik

👉LINQ w C# — przetwarzanie kolekcji bez pętli – zobacz w kursie LINQ w C# -czytelny kod, wydajne zapytania

👉 Typy wartościowe vs referencyjne w C# — jak działa pamięć – zobacz w kursie C# Podstawy Programowania: Twój Pierwszy Krok w Świat Kodowania

Dołącz do Listy VIP

I otrzymaj roadmapę Junior .NET Developer oraz najlepszą ofertę, gdy tylko ruszą zapisy!!!

Kontakt: mariuszjurczenko@dev-hobby.pl
Zero spamu. Możesz wypisać się w każdej chwili.

Dodaj komentarz