All Blogs Best Practices 4 min read

Entity Framework Core: Performance and Patterns That Scale

Sandeep Pal
June 3, 2026
Entity Framework Core: Performance and Patterns That Scale

EF Core is fast when you use it deliberately

Entity Framework Core gets blamed for slow apps, but we usually find N+1 queries, unbounded includes, or accidental client-side evaluation—not broken ORM magic. EF Core 8 improved batching, JSON mapping, and raw SQL composition. This guide covers patterns we teach before anyone touches a production database at Toolliyo.

Understand change tracking

By default, EF tracks entities returned from queries so SaveChanges knows what changed. Tracking costs memory and CPU on read-heavy paths.

  • Use AsNoTracking() for read-only lists and reports.
  • Use AsNoTrackingWithIdentityResolution() when you need consistent instances in one graph without updates.
  • Project to DTOs with Select so EF never materializes full entities you will not update.
var summaries = await db.Orders
    .AsNoTracking()
    .Where(o => o.CustomerId == customerId)
    .Select(o => new OrderSummaryDto(o.Id, o.Total, o.CreatedUtc))
    .ToListAsync(ct);

Only the columns you need hit the wire—less memory, less mapping, clearer intent.

N+1: the bug that keeps returning

Loading orders, then looping to load lines per order fires N+1 SQL round trips. Fix with explicit loading strategies:

  1. Include(o => o.Lines) when you need the whole graph.
  2. Split queries with AsSplitQuery() on SQL Server when cartesian explosion from multiple includes hurts performance.
  3. Projection when you only need nested fields in the UI.

Enable sensitive data logging locally and watch SQL output. One afternoon of log review beats a week of guessing.

Indexes and query filters

EF generates SQL; SQL Server needs indexes. Index foreign keys and columns in Where, OrderBy, and join keys. Use global query filters for soft delete (IsDeleted == false) but remember filtered entities disappear from normal queries—document that for the team.

Pagination done right

Never ToList() then Skip/Take in memory on large tables. Push paging to the database:

var page = await db.Products
    .OrderBy(p => p.Id)
    .Skip(pageIndex * pageSize)
    .Take(pageSize)
    .ToListAsync(ct);

For deep paging on huge tables, keyset pagination on Id or CreatedUtc beats large offsets.

Compiled queries and bulk operations

Compiled queries help hot paths executed millions of times with the same shape. EF Core 7+ bulk updates and deletes (ExecuteUpdateAsync, ExecuteDeleteAsync) avoid loading entities into memory just to change one column or remove rows.

await db.Orders
    .Where(o => o.Status == OrderStatus.Expired)
    .ExecuteDeleteAsync(ct);

Transactions and concurrency

Wrap multi-step writes in explicit transactions when business rules require atomicity. Use row versioning ([Timestamp] or IsRowVersion) for optimistic concurrency—catch DbUpdateConcurrencyException and merge or retry with user feedback.

When to leave EF for Dapper or raw SQL

Complex reporting, bulk ETL-shaped loads, or hand-tuned execution plans may belong in Dapper or FromSqlRaw with parameters—never string concatenation for user input. Hybrid apps use EF for OLTP and Dapper for read models; that is normal, not failure.

DbContext lifetime and pooling

Register DbContext scoped in web apps. Enable DbContext pooling for high-throughput APIs—reuse internal configuration while each request gets isolated state. Do not make DbContext singleton.

Migrations in teams

  • One migration per logical change; review generated SQL before merge.
  • Test migrations against a copy of production-like data volume.
  • Have a rollback plan—some teams prefer idempotent SQL scripts for production.

Diagnostics toolkit

Logging with LogLevel.Information for EF SQL in development, Application Insights dependency tracking in production, MiniProfiler or tagged logging with correlation IDs. Measure p95 query duration per endpoint, not average alone.

Checklist before you ship

  1. Every list endpoint paginated and projected.
  2. No unbounded Include chains.
  3. Indexes verified for hot queries.
  4. Read paths use no-tracking or DTO projection.
  5. Bulk maintenance uses ExecuteUpdate/Delete where appropriate.

EF Core rewards developers who understand SQL, not those who treat the database as a black box. Master these patterns and you will ship faster queries without abandoning the productivity that brought you to EF in the first place.

1 views 0 likes 0 comments
Comments (0)
Sign in to leave a comment
Toolliyo Assistant
Ask about tutorials, ebooks, training, pricing, mentor services, and support. I use public site content only—not admin or internal tools.

care@toolliyo.com

Need callback? Share your details