Imagine this: you’re building a blog application. A user visits your API to view their blog post and expects to see not just the post but also the comments on that post—and maybe even who made each comment. Now ask yourself this:
Do I want to make one big efficient call to the database, or should I grab each piece one by one?
If your gut says, “One and done,” then you're thinking about eager loading. So let’s dive deep into what it is, how it works, and how you can use it wisely in your ASP.NET Core Web API.
What Is Eager Loading?
Eager loading is a technique used with Entity Framework Core (EF Core) where you load related data alongside your main data in a single query. It's like ordering a combo meal instead of going back to the counter for fries and then again for your drink.
By using .Include() and .ThenInclude(), you’re telling EF Core:
Hey, while you’re grabbing that Blog, go ahead and pull in the Posts and their Comments too.
Why Use Eager Loading?
Here’s the deal
- Performance boost: One query instead of many.
- Avoid the N+1 problem: Imagine fetching 1 blog and 50 posts with separate queries—EF will hit the database 51 times!
- Cleaner API results: Users don’t have to make extra requests to get the full picture.
Real-World Example. Blog, Posts, and Comments
Let’s build a sample project:
public class Blog
{
public int BlogId { get; set; }
public string Title { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
public List<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentId { get; set; }
public string Message { get; set; }
public string AuthorName { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
}
This setup gives us:
- A Blog with many Posts
- Each Post with many Comments
DbContext Setup
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
Sample Seed Data
Let’s seed some mock data for testing:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Title = "Tech with Jane" });
modelBuilder.Entity<Post>().HasData(
new Post { PostId = 1, Content = "ASP.NET Core is awesome!", BlogId = 1 },
new Post { PostId = 2, Content = "Entity Framework Tips", BlogId = 1 }
);
modelBuilder.Entity<Comment>().HasData(
new Comment { CommentId = 1, Message = "Loved it!", AuthorName = "John", PostId = 1 },
new Comment { CommentId = 2, Message = "Very helpful!", AuthorName = "Alice", PostId = 1 },
new Comment { CommentId = 3, Message = "Great explanation.", AuthorName = "Sara", PostId = 2 }
);
}
Implementing Eager Loading in API
Here’s how to fetch a blog along with all its posts and the comments under each post:
[HttpGet("{id}")]
public async Task<IActionResult> GetBlogDetails(int id)
{
var blog = await _context.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.FirstOrDefaultAsync(b => b.BlogId == id);
if (blog == null)
return NotFound();
return Ok(blog);
}
What’s happening here?
- .Include(b => b.Posts) loads the Posts for the Blog.
- .ThenInclude(p => p.Comments) loads the Comments for each Post.
All of this is done in one SQL query under the hood. No multiple round trips.
Best Practice. Use DTOs (Data Transfer Objects)
Returning full entities (especially with nested objects) is risky—you might expose internal or sensitive data. So, let’s clean it up using a DTO:
public class BlogDto
{
public string Title { get; set; }
public List<PostDto> Posts { get; set; }
}
public class PostDto
{
public string Content { get; set; }
public List<CommentDto> Comments { get; set; }
}
public class CommentDto
{
public string Message { get; set; }
public string AuthorName { get; set; }
}
Then in your controller
[HttpGet("{id}")]
public async Task<IActionResult> GetBlogDto(int id)
{
var blog = await _context.Blogs
.Where(b => b.BlogId == id)
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.Select(b => new BlogDto
{
Title = b.Title,
Posts = b.Posts.Select(p => new PostDto
{
Content = p.Content,
Comments = p.Comments.Select(c => new CommentDto
{
Message = c.Message,
AuthorName = c.AuthorName
}).ToList()
}).ToList()
})
.FirstOrDefaultAsync();
if (blog == null) return NotFound();
return Ok(blog);
}
Now your API returns only what the client needs. Clean. Efficient. Secure.
Important Things to Remember
Use eager loading when
- You know you’ll need related data.
- You want to optimize performance with fewer database hits.
Avoid eager loading when
- The related data is huge, and you don’t need all of it.
- It slows down the response or affects performance negatively.
GitHub Project Link
Output
![]()
Conclusion
Eager loading is a powerful feature in ASP.NET Core Web API that allows you to retrieve related data efficiently using Entity Framework Core. By leveraging .Include() and .ThenInclude(), you can eliminate unnecessary database queries, solve the N+1 problem, and improve the performance of your API. However, it’s essential to use eager loading wisely—only include the data you need and consider using DTOs to keep responses clean and secure. When applied correctly, eager loading helps you build faster, more scalable, and well-structured APIs.