Entity Framework Core Performance Best Practices Print

  • 0

Optimizing EF Core Performance

Essential tips for improving Entity Framework Core performance on Plesk Windows hosting.

1. Use Async Methods

Always use async for database operations:

// Good
var products = await _context.Products.ToListAsync();
await _context.SaveChangesAsync();

// Avoid
var products = _context.Products.ToList();
_context.SaveChanges();

2. Avoid N+1 Queries

Use eager loading with Include():

// Bad: N+1 queries
var orders = await _context.Orders.ToListAsync();
foreach (var order in orders)
{
    var items = order.Items; // Additional query for each order!
}

// Good: Single query with Include
var orders = await _context.Orders
    .Include(o => o.Items)
    .Include(o => o.Customer)
    .ToListAsync();

3. Use Projections (Select)

Only retrieve needed columns:

// Bad: Retrieves all columns
var products = await _context.Products.ToListAsync();

// Good: Only needed data
var products = await _context.Products
    .Select(p => new ProductDto
    {
        Id = p.Id,
        Name = p.Name,
        Price = p.Price
    })
    .ToListAsync();

4. Use No-Tracking for Read-Only Queries

// Single query
var products = await _context.Products
    .AsNoTracking()
    .ToListAsync();

// Or configure globally for read-heavy apps
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

5. Implement Pagination

public async Task<PagedResult<Product>> GetProductsAsync(int page, int pageSize)
{
    var query = _context.Products.AsNoTracking();

    var totalItems = await query.CountAsync();
    var items = await query
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    return new PagedResult<Product>
    {
        Items = items,
        TotalCount = totalItems,
        Page = page,
        PageSize = pageSize
    };
}

6. Use Compiled Queries for Hot Paths

private static readonly Func<AppDbContext, int, Task<Product?>> _getProductById =
    EF.CompileAsyncQuery((AppDbContext context, int id) =>
        context.Products.FirstOrDefault(p => p.Id == id));

public async Task<Product?> GetByIdAsync(int id)
{
    return await _getProductById(_context, id);
}

7. Batch Operations

// Bad: Multiple round trips
foreach (var product in products)
{
    _context.Products.Add(product);
    await _context.SaveChangesAsync(); // Save each time!
}

// Good: Single round trip
_context.Products.AddRange(products);
await _context.SaveChangesAsync();

8. Index Your Columns

Add indexes for frequently queried columns:

public class Product
{
    public int Id { get; set; }

    [MaxLength(100)]
    public string Name { get; set; }

    public string Sku { get; set; }  // Frequently searched
}

// In DbContext OnModelCreating
modelBuilder.Entity<Product>()
    .HasIndex(p => p.Sku)
    .IsUnique();

9. Use Split Queries for Complex Includes

var orders = await _context.Orders
    .Include(o => o.Items)
    .Include(o => o.Payments)
    .AsSplitQuery()  // Separate queries to avoid cartesian explosion
    .ToListAsync();

Performance Checklist

  • ☐ Use async methods throughout
  • ☐ Use Include() to prevent N+1
  • ☐ Use Select() projections
  • ☐ Use AsNoTracking() for reads
  • ☐ Implement pagination
  • ☐ Add database indexes
  • ☐ Batch insert/update operations
  • ☐ Monitor with logging (in development)

Was this answer helpful?

« Back