Optimizing Database Communication in EF Core: Common Mistakes to Avoid

Osama HaiDer
2 min readJun 26, 2024

--

As a backend .NET engineer, delivering optimal performance in your applications is crucial. Entity Framework Core (EF Core) is a powerful ORM that simplifies database interactions, but it’s easy to make mistakes that can negatively impact performance. Let’s explore some common pitfalls and how to avoid them.

1. Casting IQueryable to IEnumerable

Mistake: Casting IQueryable to IEnumerable too early.

// Inefficient: Casting IQueryable to IEnumerable
var products = dbContext.Products
.Where(p => p.Price > 100)
.AsEnumerable()
.Where(p => p.Stock > 0)
.ToList();

Explanation: When you cast IQueryable to IEnumerable, the query is executed immediately, and further filtering happens in memory. This can lead to unnecessary data being loaded into memory.

Solution: Keep the query as IQueryable until the final execution.

// Efficient: Keep IQueryable until final execution
var products = dbContext.Products
.Where(p => p.Price > 100 && p.Stock > 0)
.ToList();

2. Not Using .AsNoTracking()

Mistake: Not using .AsNoTracking() when querying read-only data.

// Without AsNoTracking
var product = dbContext.Products.FirstOrDefault(p => p.Id == productId);

Explanation: By default, EF Core tracks the changes of entities. For read-only operations, this tracking adds unnecessary overhead.

Solution: Use .AsNoTracking() for read-only queries to improve performance.

// With AsNoTracking
var product = dbContext.Products
.AsNoTracking()
.FirstOrDefault(p => p.Id == productId);

3. Improper Use of .Include()

Mistake: Using .Include() excessively or incorrectly.

// Inefficient: Including unnecessary related data
var orders = dbContext.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ToList();

Explanation: Including related data that isn’t needed for the operation can result in larger queries and slower performance.

Solution: Only include related entities when necessary.

// Efficient: Include only necessary related data
var orders = dbContext.Orders
.Include(o => o.Customer)
.ToList();

4. Lack of Pagination

Mistake: Retrieving all records without pagination.

// Inefficient: Retrieving all records
var products = dbContext.Products.ToList();

Explanation: Loading large datasets into memory at once can significantly impact performance and degrade the user experience. When your database contains thousands or even millions of records, fetching all of them in a single query can result in long response times and high memory usage.

Solution: Implement pagination to load data in smaller, more manageable chunks. This improves performance and provides a better user experience by allowing users to navigate through data in pages.

Example: Here’s how to implement pagination using Skip and Take methods:

// Efficient: Implementing pagination
int pageNumber = 1;
int pageSize = 20;

var products = dbContext.Products
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();

Explanation: In this example, pageNumber represents the current page number, and pageSize represents the number of records per page. The Skip method skips the records from the previous pages, and the Take method fetches the specified number of records for the current page. This approach ensures that only a subset of the data is loaded into memory, improving performance and scalability.

By implementing pagination, you can handle large datasets more efficiently, resulting in faster response times and a smoother user experience.

--

--

Osama HaiDer
Osama HaiDer

Written by Osama HaiDer

SSE at TEO International | .Net | Azure | AWS | Web APIs | C#

Responses (1)