EF Core Quick Reference
DbContext Configuration
// Dependency Injection
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// With Pooling (20-30% faster)
services.AddDbContextPool<AppDbContext>(options =>
options.UseSqlServer(connectionString), poolSize: 128);
// DbContextFactory (for long-lived services)
services.AddDbContextFactory<AppDbContext>(options =>
options.UseSqlServer(connectionString));
Querying
// Basic query
var products = await context.Products.ToListAsync();
// With filtering
var active = await context.Products
.Where(p => p.IsActive)
.ToListAsync();
// With ordering
var sorted = await context.Products
.OrderBy(p => p.Name)
.ToListAsync();
// Single result
var product = await context.Products.FindAsync(id);
var product = await context.Products.FirstAsync(p => p.Id == id);
var product = await context.Products.SingleAsync(p => p.Id == id);
// AsNoTracking (read-only, 20-30% faster)
var products = await context.Products
.AsNoTracking()
.ToListAsync();
Loading Strategies
// Eager Loading
var blogs = await context.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Author)
.ToListAsync();
// Filtered Include
var blogs = await context.Blogs
.Include(b => b.Posts.Where(p => p.IsPublished))
.ToListAsync();
// Split Query (avoid cartesian explosion)
var blogs = await context.Blogs
.Include(b => b.Posts)
.Include(b => b.Authors)
.AsSplitQuery()
.ToListAsync();
// Projection (best performance)
var products = await context.Products
.Select(p => new ProductDto
{
Id = p.Id,
Name = p.Name,
CategoryName = p.Category.Name
})
.ToListAsync();
// Explicit Loading
await context.Entry(blog)
.Collection(b => b.Posts)
.LoadAsync();
CRUD Operations
// Create
var product = new Product { Name = "Laptop", Price = 999 };
context.Products.Add(product);
await context.SaveChangesAsync();
// Read
var product = await context.Products.FindAsync(id);
// Update
product.Price = 899;
await context.SaveChangesAsync();
// Update (disconnected)
context.Products.Update(product);
await context.SaveChangesAsync();
// Delete
context.Products.Remove(product);
await context.SaveChangesAsync();
// Batch operations
context.Products.AddRange(products);
context.Products.UpdateRange(products);
context.Products.RemoveRange(products);
await context.SaveChangesAsync();
Relationships
// One-to-Many
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogId);
// One-to-One
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);
// Many-to-Many (simple)
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts);
// Many-to-Many (with join entity)
modelBuilder.Entity<StudentCourse>()
.HasKey(sc => new { sc.StudentId, sc.CourseId });
Migrations
# Create migration
dotnet ef migrations add MigrationName
# Apply migrations
dotnet ef database update
# Rollback
dotnet ef database update PreviousMigration
# Generate script
dotnet ef migrations script --idempotent
# Remove last migration
dotnet ef migrations remove
Performance Optimization
// AsNoTracking for read-only
var products = await context.Products
.AsNoTracking()
.ToListAsync();
// Compiled Queries
private static readonly Func<AppDbContext, int, Task<Product>> _getProduct =
EF.CompileAsyncQuery((AppDbContext context, int id) =>
context.Products.FirstOrDefault(p => p.Id == id));
// Projection over full entity
var dtos = await context.Products
.Select(p => new { p.Id, p.Name })
.ToListAsync();
// Split queries
var result = await context.Orders
.Include(o => o.Items)
.Include(o => o.Customer)
.AsSplitQuery()
.ToListAsync();
Advanced Patterns
// Global Query Filter
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted);
// Ignore filter
var all = await context.Products
.IgnoreQueryFilters()
.ToListAsync();
// Optimistic Concurrency
[Timestamp]
public byte[] RowVersion { get; set; }
// Handle conflict
try {
await context.SaveChangesAsync();
} catch (DbUpdateConcurrencyException ex) {
// Resolve conflict
}
// Raw SQL
var products = await context.Products
.FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {minPrice}")
.ToListAsync();
// Execute SQL
await context.Database
.ExecuteSqlRawAsync("UPDATE Products SET Price = Price * 1.1");
Common Patterns
// Repository Pattern
public interface IRepository\<T\> where T : class
{
Task\<T\> GetByIdAsync(int id);
Task<IEnumerable\<T\>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}
// Unit of Work
public interface IUnitOfWork
{
IRepository<Product> Products { get; }
IRepository<Order> Orders { get; }
Task<int> CommitAsync();
}
Indexing
// Single column
modelBuilder.Entity<Product>()
.HasIndex(p => p.Name);
// Composite index
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.OrderDate });
// Unique index
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
// Filtered index
modelBuilder.Entity<Product>()
.HasIndex(p => p.Price)
.HasFilter("[IsDeleted] = 0");
Performance Checklist
- Use AsNoTracking for read-only queries
- Enable DbContext pooling
- Avoid N+1 queries (use Include or projection)
- Use projection instead of full entities
- Implement proper indexes
- Use compiled queries for hot paths
- Use AsSplitQuery for multiple includes
- Batch operations instead of individual SaveChanges
- Use raw SQL for complex queries
- Enable connection resiliency
Anti-Patterns to Avoid
// ❌ N+1 Query Problem
foreach (var order in orders) {
var customer = await context.Customers.FindAsync(order.CustomerId);
}
// ❌ Tracking for read-only
var products = await context.Products.ToListAsync(); // Slow!
// ❌ Loading everything
var allOrders = await context.Orders.ToListAsync(); // Millions of rows!
// ❌ Cartesian explosion
var blogs = await context.Blogs
.Include(b => b.Posts)
.Include(b => b.Authors)
.ToListAsync(); // Huge result set!
// ❌ SQL Injection
var sql = $"SELECT * FROM Products WHERE Name = '{userInput}'";
Connection Strings
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=MyDb;Trusted_Connection=True;",
"PostgreSQL": "Host=localhost;Database=mydb;Username=postgres;Password=pass",
"MySQL": "Server=localhost;Database=mydb;User=root;Password=pass;",
"SQLite": "Data Source=app.db"
}
}
Testing
// In-Memory Database
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// SQLite (better)
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=:memory:"));
// TestContainers (production-like)
await using var container = new MsSqlBuilder()
.WithPassword("Pass123!")
.Build();
await container.StartAsync();