Hi everyone, In this post, I'll share the most common mistakes developers (even experienced ones) make when working with Entity Framework Core. I've seen these mistakes many times — both in my own projects and in others' code. More importantly, I'll explain why these mistakes are a problem and how to fix them with simple, clear code examples.

1. Not Using Indexes

Problem: When you filter large tables without indexes, the database has to scan every single row. This makes your queries much slower.

Example (Bad):

SELECT * FROM Orders WHERE CustomerId = 5

Without an index, the database reads the whole table.

Solution: Create indexes on frequently filtered columns.

SQL Example:

CREATE INDEX IX_Orders_CustomerId ON Orders (CustomerId)

EF Core Example:

modelBuilder.Entity<Order>()
    .HasIndex(o => o.CustomerId);

This improves the performance of queries that use WHERE CustomerId = X.

2. Not Using Projections

Problem: When you query an entity with many properties but only need a few, you waste memory and network bandwidth by loading unnecessary data.

Example (Bad):

var orders = context.Orders.ToList();

This fetches all fields from the table.

Solution: Use projections to select only the fields you need.

Example (Good):

var orderSummaries = context.Orders
    .Select(o => new { o.OrderDate, o.TotalAmount })
    .ToList();

This makes queries faster and reduces memory usage, especially useful for API or mobile apps

3. Overfetching Data

Problem: Loading thousands of records at once can cause high memory usage and slow down your application.

Example (Bad):

var customers = context.Customers.ToList();

Solution: Use paging with Skip and Take.

Example (Good):

int pageSize = 50;
int pageNumber = 2;

var customers = context.Customers
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize)
    .ToList();

This way, you only load the data you actually need.

4. Not Using AsNoTracking() for Read-Only Queries

Problem: By default, EF Core tracks changes on all entities. But if you just need to read data, this adds extra overhead.

Example (Bad):

var products = context.Products.ToList();

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

Example (Good):

var products = context.Products
    .AsNoTracking()
    .ToList();

This improves performance because EF Core doesn't need to track these objects in memory.

5. Using Eager Loading Without Filters

Problem: When you use Include and ThenInclude to load related data, you might load too much unnecessary data, slowing down the query.

Example (Bad):

var orders = context.Orders
    .Include(o => o.Customer)
    .Include(o => o.OrderItems)
    .ThenInclude(oi => oi.Product)
    .ToList();

If you don't need all of this, avoid loading it.

Solution: Filter the related data or only load what you need.

Example (Better):

var recentOrders = context.Orders
    .Include(o => o.OrderItems.Where(oi => oi.Quantity > 5))
    .ToList();

This fetches only the necessary related data, reducing load time and memory usage.

6. Not Using Transactions in Batch Operations

Problem: When using ExecuteUpdate or ExecuteDelete, these operations are not tracked by EF Core, and if a problem occurs, you can't roll them back with SaveChanges().

Example (Bad):

context.Users.ExecuteUpdate(...);
// No rollback if something fails.

Solution: Use a manual transaction.

Example (Good):

using var transaction = context.Database.BeginTransaction();
context.Users.ExecuteUpdate(...);
context.SaveChanges();
transaction.Commit();

This keeps your data consistent and safe in case of errors.

7. Ignoring Concurrency Control

Problem: If two users update the same data at the same time, one user's changes might overwrite the other's without warning.

Solution: Use a concurrency token (like a RowVersion column) to detect conflicts.

Example:

[Timestamp]
public byte[] RowVersion { get; set; }

When saving changes:

try
{
    context.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
    // Handle the conflict
}

This prevents data loss in concurrent environments.

8. Not Using Migrations

Problem: If you manually update your database schema, it may no longer match your EF Core model.

Solution: Use EF Core migrations to manage database changes.

Example:

dotnet ef migrations add AddProductTable
dotnet ef database update

This keeps your database schema synchronized with your code.

9. Not Disposing DbContext

Problem: If you create a DbContext manually and don't dispose of it, it can cause memory leaks.

Example (Bad):

var context = factory.CreateDbContext();

Solution: Use a using block.

Example (Good):

using var context = factory.CreateDbContext();

This ensures the context is cleaned up properly after use.

10. Not Using Async Methods

Problem: Synchronous queries can block threads and reduce application responsiveness, especially in high-traffic web applications.

Example (Bad):

var users = context.Users.ToList();

Solution: Use asynchronous methods.

Example (Good):

var users = await context.Users.ToListAsync();

Async methods help your application handle more requests at the same time by freeing up threads during I/O operations.

Final Thoughts

Even though EF Core is a powerful and flexible ORM, small mistakes can cause serious performance problems and data issues. By fixing these 10 mistakes, you'll write faster, safer, and more reliable applications.

If you've experienced similar situations or have other tips, feel free to share them.

I hope this post was helpful!