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();