What is the difference between lazy loading, eager loading and explicit loading?

Entity Framework & EF Core Querying Intermediate 2–3 Yrs3–5 Yrs Conceptual 2 min read

Quick Answer

Eager loading pulls related data in the same query using Include(). Lazy loading fetches a navigation property automatically the first time you access it — convenient but the classic cause of the N+1 query problem. Explicit loading loads a navigation on demand with context.Entry(x).Collection(...).Load(). Prefer eager loading (or projection) so you control how many round trips happen.

Eager loading is when I ask for the related data up front with Include, so it comes back in one query with joins. Lazy loading is when EF fetches a navigation property automatically the moment I touch it — it's convenient but it's how you get the N+1 problem: loop over 100 orders, touch order.Lines each time, and that's 101 queries. Explicit loading is the middle ground where I call Load myself when I actually need it. My default is eager loading with Include, or better, projecting straight into a DTO with Select so EF pulls exactly the columns I need and doesn't track anything. I catch N+1 by watching the SQL EF logs.

Eager — _db.Orders.Include(o => o.Lines).ThenInclude(l => l.Product): one query (or a small fixed number) with joins. Predictable.

Lazy — with the proxies package and virtual navigations, accessing order.Lines silently issues a query. In a loop over 100 orders that's 1 + 100 queries — the N+1 problem. It also breaks once the context is disposed.

Explicit — you decide: _db.Entry(order).Collection(o => o.Lines).Load(), or .Query() to filter what you load. Useful when you only sometimes need the related data.

Projection is often best: Select straight into a DTO with exactly the fields you need — EF builds one query and doesn't track anything.

Diagnose N+1 by logging SQL (EF Core logs every command) or using a profiler; the symptom is a burst of near-identical queries.

C# — N+1 vs eager vs projection
// N+1: 1 query for orders + 1 per order for Lines (lazy loading)
foreach (var o in _db.Orders.ToList())
    total += o.Lines.Sum(l => l.Amount);

// Eager: one query with a join
var orders = _db.Orders.Include(o => o.Lines).ToList();

// Projection: one query, only the columns needed, no tracking
var totals = _db.Orders
    .Select(o => new { o.Id, Total = o.Lines.Sum(l => l.Amount) })
    .ToList();