Skip to main content

EF Core Advanced Patterns

DbContext Best Practices

Scoped Lifetime

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));

DbContext Pooling

builder.Services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(connectionString));

Query Optimization

❌ N+1 Problem

var orders = await context.Orders.ToListAsync();
foreach (var order in orders)
{
var customer = await context.Customers.FindAsync(order.CustomerId); // N queries!
}

✅ Eager Loading

var orders = await context.Orders
.Include(o => o.Customer)
.ToListAsync();

✅ Projection (Best)

var orders = await context.Orders
.Select(o => new OrderDto
{
Id = o.Id,
CustomerName = o.Customer.Name
})
.ToListAsync();

Split Queries

var posts = await context.Posts
.Include(p => p.Comments)
.Include(p => p.Tags)
.AsSplitQuery() // Prevents cartesian explosion
.ToListAsync();

No Tracking

var products = await context.Products
.AsNoTracking() // Read-only, better performance
.ToListAsync();

Global Query Filters

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}

Compiled Queries

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

var product = await GetProductById(context, productId);

Bulk Operations

// EF Core 7+
await context.Products
.Where(p => p.Price < 10)
.ExecuteUpdateAsync(s => s.SetProperty(p => p.Price, p => p.Price * 1.1));

await context.Products
.Where(p => p.IsDeleted)
.ExecuteDeleteAsync();