Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Object Relational Mapping  ›  Lesson

The Change Tracker — How EF Knows What to Save

Change Tracker API 14 minEF Core's change tracker records the state of every entity a DbContext loads or is given, and SaveChanges turns those states into INSERT, UPDATE and DELETE statements.

The Change Tracker — How EF Knows What to Save

To mark a loan returned, ReadingRoom loads the loan, sets ReturnedAt, and calls SaveChangesAsync. No UPDATE statement anywhere, yet the database changes. The piece that makes this work is the change tracker, and understanding it explains most EF surprises.

The rule

Every tracked entity has a state; SaveChanges turns states into SQL.

A DbContext tracks each entity it loads or is handed, in one of five states:

State Meaning SaveChanges sends
Added new, not yet in the database INSERT
Unchanged same as when loaded nothing
Modified a property changed UPDATE (changed columns)
Deleted marked for removal DELETE
Detached not tracked at all nothing
var loan = await db.Loans.FindAsync(40);      // tracked: Unchanged
loan!.ReturnedAt = DateTime.UtcNow;           // becomes Modified at save time
await db.SaveChangesAsync();                  // UPDATE Loans SET ReturnedAt = ... WHERE Id = 40

db.Loans.Add(new Loan { BookId = 3, MemberId = 5 });   // Added
db.Members.Remove(oldMember);                          // Deleted
await db.SaveChangesAsync();                           // INSERT and DELETE in one transaction

When an entity is loaded, EF keeps a snapshot of its original values. During SaveChanges it compares each tracked entity with its snapshot to find what changed. All the statements from one SaveChanges run in a single transaction: all succeed or none do.

The Change Tracker API lets you look inside: db.Entry(loan).State shows one entity's state, db.ChangeTracker.Entries() lists them all, and db.ChangeTracker.DebugView.LongView prints a readable report.

Read-only queries do not need any of this. db.Books.AsNoTracking().ToListAsync() skips the snapshots, which saves memory and time on big lists.

The shortcut

Before each SaveChanges, say the state of every entity you touched. The list of states is the list of SQL statements that will run.

One SaveChanges, three statementsStep 1 of 4
  1. Load member 5

    FindAsync(5) — Unchanged. No SQL at save time unless it changes.

The trap: changing an entity loaded with AsNoTracking() and expecting SaveChanges to write it. Nothing is tracking it, so nothing is saved.

Drills

Answer fast — trust the rule.

quick drill

An entity was just loaded and nothing was changed. What is its state?

quick drill

What does SaveChangesAsync send for an entity in the Deleted state?

quick drill

You load books with AsNoTracking(), change a title, and call SaveChanges. What happens?

quick drill

Why use AsNoTracking() for the /books listing?

Remember

  • Every tracked entity has a state; SaveChanges turns states into SQL.
  • Name each touched entity's state before saving; that list is the SQL.
  • AsNoTracking() for read-only queries — and nothing it returns gets saved.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainReturn a book without writing SQL

Explain step by step what EF Core's change tracker does when ReadingRoom loads loan 40, sets ReturnedAt, and calls SaveChangesAsync. Then explain why the /books listing endpoint should use AsNoTracking().