database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

By aaronontheweb · 580 installs

npx skills add aaronontheweb/dotnet-skills --skill database-performance

Source repository · Upstream listing

Database Performance Patterns When to Use This Skill Use this skill when: Designing data access layers Optimizing slow database queries Choosing between EF Core and Dapper Avoiding common performance pitfalls Core Principles 1. Separate read and write models Don't use the same types for both 2. Think in batches Avoid N+1 queries 3. Only retrieve what you need No SELECT 4. Apply row limits Always have a configurable Take/Limit 5. Do joins in SQL Never in application code 6. AsNoTracking for reads EF Core change tracking is expensive Read/Write Model Separation (CQRS Pattern) Read and write models are fundamentally different they have different shapes, columns, and purposes. Don't create a single "User" entity and reuse it everywhere. Read models are denormalized, optimized for query efficiency, and return multiple projection types (UserProfile, UserSummary, UserDetailForAdmin) Write models are normalized, validation focused, and accept strongly typed commands (CreateUserCommand, UpdateUserCommand) Architecture Read Store Interface Write Store Interface Key structural differences illustrated: Read store returns multiple different DTOs (UserProfile, UserSummary, bool flag) Write store returns minimal data (just UserId on create) or void Read queries are stateless projections no tracking needed Write operations focus on command validation, not retrieving data afterwards Different databases/tables can back read vs write (eventual consistency pattern) Always Apply Row Limits Never return unbounded result sets. Every read method should have a configurable limit. Pattern: Limit Parameter EF Core with Pagination AsNoTracking for Read Queries EF Core's change tracking is expensive. Disable it for read only queries. Configure Default Behavior Then explicitly enable tracking when needed: Avoid N+1 Queries The N+1 problem: fetching a list, then querying for each item's related data. The Problem Solution 1: Include (EF Core) Solution 2: Batch Query (Dapper) Never Do Application Side Joins Joins must happen in SQL, not in C . Avoid Cartesian Explosions Multiple Include calls can cause Cartesian products. Solution: Split Queries Solution: Explicit Projection Constrain Column Sizes Define maximum lengths in your EF Core model to prevent oversized data. Don't Build Generic Repositories Generic repositories hide query complexity and make optimization difficult. Problems with generic repositories: Can't optimize specific queries No way to enforce limits Hide N+1 problems Make it easy to fetch too much data Encourage lazy thinking about data access Dapper for Read Heavy Workloads For complex read queries, Dapper with explicit SQL is often cleaner and faster. When to Use EF Core vs Dapper Scenario Recommendation Simple CRUD EF Core Complex read queries Dapper Writes with validation EF Core Bulk operations Dapper or raw SQL Reporting/analytics Dapper Domain heavy writes EF Core You can use both in the same project EF Core for writes, Dapper for reads. Quick Reference Anti Pattern Solution No row limit Add limit parameter to every read method SELECT Project only needed columns N+1 queries Use Include or batch queries Application joins Do joins in SQL Cartesian explosion Use AsSplitQuery or projection Tracking read only data Use AsNoTracking Generic repository Purpose built read/write stores Unbounded strings Configure MaxLength in model Resources EF Core Performance : https://learn.microsoft.com/en us/ef/core/performance/ Dapper : https://github.com/DapperLib/Dapper AsSplitQuery : https://learn.microsoft.com/en us/ef/core/querying/single split queries