Entity Framework (EF) is a powerful ORM (Object-Relational Mapper) for .NET, and LINQ (Language Integrated Query) is the primary way to interact with EF data models. Mastering LINQ not only helps you write clean, expressive, and efficient queries but also prevents common pitfalls like performance issues.
1. Defer Execution and IQueryable
EF queries using IQueryable are not executed immediately. Instead, they are built into an expression tree and executed only when iterated (e.g., via ToList(), FirstOrDefault(), etc.).
var query = _context.Orders.Where(o => o.CustomerId == 5); // No DB hit yet
var list = query.ToList(); // Now the SQL query is sent
✅ Rule:
Always build your full query before calling .ToList() or any terminal operator.
2. Avoid Calling .ToList() Too Early
Calling .ToList()
early materializes the result and breaks query composition, which means:
- You lose the ability to add filters, sorting, and includes efficiently.
- It might bring more data into memory than necessary.
🚫 Bad:
var orders = _context.Orders.ToList();
var filtered = orders.Where(o => o.Total > 100); // This runs in memory
✅ Good:
var filtered = _context.Orders.Where(o => o.Total > 100); // Query runs in SQL
3. Use Select to Project Data
Avoid selecting entire entities if you only need a few fields. This improves performance.
🚫 Bad:
var customers = _context.Customers.ToList(); // Loads all columns
✅ Good:
var names = _context.Customers.Select(c => c.Name).ToList();
4. Eager Loading with .Include()
To avoid the N+1 problem, use .Include() to load related data.
var orders = _context.Orders.Include(o => o.Customer).ToList();
Without .Include()
, accessing order.Customer
will trigger separate queries if lazy loading is enabled.
5. Pagination Using Skip and Take
Efficiently handle large datasets using .Skip() and .Take() for pagination.
int page = 2;
int pageSize = 10;
var pagedOrders = _context.Orders
.OrderBy(o => o.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
6. Query Filters and Reusability
You can reuse LINQ query definitions:
var query = _context.Orders.Where(o => o.Status == "Active");
var top10 = query.OrderBy(o => o.Date).Take(10).ToList();
var count = query.Count();
7. Avoid N+1 Query Problem
This happens when you loop through a list and access navigation properties, causing multiple DB calls.
🚫 Bad:
var orders = _context.Orders.ToList();
foreach (var order in orders)
{
Console.WriteLine(order.Customer.Name); // Triggers extra query per order
}
✅ Good:
var orders = _context.Orders.Include(o => o.Customer).ToList();
8. Use Any() Instead of Count() for Existence Checks
var exists = _context.Orders.Any(o => o.Status == "Pending");
Using Count()
to check existence is less efficient.
Bonus Tips
🚫 No SQL is sent to the database until you do something like:
.ToList() / .ToListAsync()
.FirstOrDefault() / .FirstOrDefaultAsync()
.Count() / .CountAsync()
.Any() / .AnyAsync()
.Sum() / etc.
Only then will EF Core execute the SQL against the database.
Final Tips:
- Use
AsNoTracking()
when you only need to read data (for performance). - Prefer method syntax over query syntax for consistency.
- Profile queries using tools like EF Profiler or SQL Profiler if you're concerned about performance.
By following these LINQ best practices in Entity Framework, you can write performant, maintainable, and cleaner data access code in your applications.
If you found this helpful, consider supporting my work at ☕ Buy Me a Coffee.