Entity Framework Core Internals – What Happens Under the Hood

You call await context.SaveChangesAsync() and data magically persists to SQL Server, PostgreSQL, or SQLite. But EF Core is no black box—it is a finely-tuned pipeline of caches, change maps, translators, and ADO.NET commands. Understanding that machinery turns mysterious slow-downs into fixable engineering tasks.

Real-Life Analogy: Airport Baggage System

Each checked bag (entity) gets a tag (primary key) at the counter (DbContext). Conveyors (change tracker) route it, scanners (query compiler) inspect its contents, routers (query provider) choose the correct belt (SQL), and ground crew (relational provider) load it onto the aircraft (database). Lose a tag and the bag goes missing—much like an entity without a key.

The Five Core Engines

  1. Model Cache – builds once per DbContextOptions; holds entity shapes, relationships, indexes.
  2. Change Tracker – watches property values, keeps an Identity Map, and records EntityState (Added, Modified, …).
  3. LINQ Provider – converts expression trees into a query pipeline tree.
  4. Query Compiler – turns the pipeline into provider-agnostic Query Commands.
  5. Database Provider – renders SQL, creates DbParameter objects, executes via ADO.NET, and materialises results.

Deep Dive: Query Path

// Cold path
var recent = await ctx.Flights
                     .Where(f => f.DepartsOn > DateTime.UtcNow)
                     .ToListAsync();
  • Expression Tree Capture – normal LINQ delegates freeze into data.
  • Pipeline Translation – visitor pattern walks the tree, producing a Shaped Query Expression.
  • SQL Generation – provider builds the final text and parameters. Call recent.ToQueryString() to inspect.
  • Relational Execution – command uses DbDataReader; EF’s Materialiser emits IL that hydrates entities fast.

Deep Dive: Save Path

  1. DetectChanges() – compares snapshots; optional in high-perf loops (AutoDetectChangesEnabled).
  2. State Graph – builds update entries;
  3. Command Batching – groups by table up to MaxBatchSize (42 default, configurable).
  4. Concurrency Checks – adds WHERE RowVersion = @p to catch overwrites.
  5. Commit – executed within an implicit transaction.

Diagnostics Toolkit

  • .ToQueryString() ➜ quick SQL preview.
  • Logging category Microsoft.EntityFrameworkCore.Database.Command ➜ full SQL + timing.
  • TagWith("MyQuery") ➜ bookmarks in SQL Profiler.
  • Performance Analyzers ➜ dotnet-ef dbcontext optimize.

Performance Guidelines

  • Reuse DbContext per web request; disposing frequently thrashes the model cache.
  • Turn off tracking for read-only pages (AsNoTracking()).
  • Batch big insertsBulkInsert packages or EF8’s ExecuteUpdate().

Final Thoughts

Once you see EF Core as a chain of small, replaceable engines, performance tuning becomes surgical: profile, locate the slow engine, and tweak. No more superstition—just informed optimisation.

Entity Framework Core Internals – What Happens Under the Hood | SimplyAdvanced.dev