100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Eager, Lazy, and Explicit Loading

The three strategies EF Core offers for loading related data, and how choosing the wrong one causes the N+1 query problem.

QueryingIntermediate10 min readJul 10, 2026
Analogies

When an entity has navigation properties, such as a Blog with a collection of Posts, EF Core will not populate them automatically unless you tell it to. There are three strategies: eager loading, which fetches related data as part of the initial query using Include(); lazy loading, which fetches related data automatically the first time a navigation property is accessed; and explicit loading, which fetches related data on demand via the Entry() API after the parent entity is already loaded. Each strategy trades off query count, memory usage, and control differently.

🏏

Cricket analogy: It is like three ways a captain gets fielding data: pre-match, the whole fielding plan is briefed at once (eager); during play, a fielder is repositioned the instant a specific batter comes in (lazy); or the captain calls a specific field change only when he decides to (explicit).

Eager Loading with Include and ThenInclude

Eager loading uses Include() to specify a navigation property to load as part of the original query, and ThenInclude() to chain into a further level of nested navigation, for example context.Blogs.Include(b => b.Posts).ThenInclude(p => p.Comments). EF Core generates a single SQL query using JOINs (or, since EF Core 5, split queries via AsSplitQuery() to avoid a cartesian explosion) so related data arrives in the same round trip as the parent entities, which is usually the most predictable and performant option for data you know you will need.

🏏

Cricket analogy: It is like a broadcaster pulling player stats, career average, current form, and head-to-head record, all in one combined graphic package before the toss, rather than fetching each stat separately as commentary progresses.

csharp
// Eager loading: one round trip fetches blogs, their posts, and each post's comments
var blogs = await context.Blogs
    .Include(b => b.Posts)
        .ThenInclude(p => p.Comments)
    .AsSplitQuery() // avoids a JOIN cartesian explosion for multiple collections
    .ToListAsync();

Lazy Loading and the N+1 Trap

Lazy loading requires the Microsoft.EntityFrameworkCore.Proxies package, UseLazyLoadingProxies() in your DbContext configuration, and navigation properties marked virtual. Once enabled, simply accessing blog.Posts triggers a fresh database query at that moment, generating a runtime proxy that intercepts the property getter. The danger is the N+1 problem: iterating over 50 blogs and accessing .Posts inside the loop fires one query for the blogs plus 50 additional queries, one per blog, instead of a single JOIN-based query.

🏏

Cricket analogy: It is like a scorer who only looks up a batter's career strike rate the instant that batter walks in to bat, meaning across an 11-man innings you make 11 separate trips to the record book instead of pulling the whole squad's stats beforehand.

Lazy loading is convenient but dangerous inside loops. Iterating over a collection and accessing a navigation property per item generates one query per iteration. Prefer eager loading with Include() whenever you know upfront which related data a request will need.

Explicit Loading with the Entry API

Explicit loading gives you precise, on-demand control without proxies or virtual properties. Using context.Entry(blog).Collection(b => b.Posts).LoadAsync() for collections or context.Entry(post).Reference(p => p.Author).LoadAsync() for single references, you decide exactly when related data is fetched, often useful in conditional branches where you only need related data under specific circumstances, such as loading comments only if a post's status is 'Published'.

🏏

Cricket analogy: It is like a team analyst who pulls up a bowler's spell-by-spell breakdown only when the captain specifically asks for it mid-match, a deliberate, conditional lookup rather than an automatic or blanket one.

Explicit loading also supports filtered loading: context.Entry(blog).Collection(b => b.Posts).Query().Where(p => p.IsPublished).Load() lets you load only a subset of a related collection instead of the entire set.

  • Eager loading (Include/ThenInclude) fetches related data in the same query as the parent, typically the safest default.
  • Lazy loading fetches related data automatically the first time a navigation property is accessed, requiring the Proxies package and virtual properties.
  • Lazy loading inside a loop causes the N+1 problem: one query per iteration instead of a single batched query.
  • Explicit loading via context.Entry().Collection()/Reference().Load() gives on-demand, conditional control.
  • AsSplitQuery() avoids cartesian-explosion row duplication when eagerly loading multiple collection navigations.
  • Explicit loading supports filtered queries on the related collection via .Query().Where(...).
  • Choosing the right strategy is a tradeoff between query count, payload size, and code simplicity.

Practice what you learned

Was this page helpful?

Topics covered

#EntityFrameworkCoreStudyNotes#MicrosoftTechnologies#EagerLazyAndExplicitLoading#Eager#Lazy#Explicit#Loading#StudyNotes#SkillVeris#ExamPrep