We’ve all been there. Staring at the screen, deep into a debugging session, absolutely convinced the code should work. The logic is sound, the syntax is perfect, but the output is just… wrong. I recently lost an afternoon to one of these phantom bugs, a frustrating journey that led me deep into the inner workings of the .NET Entity Framework.

The culprit? A feature called Identity Resolution and a classic mistake in managing the Database Context. If you’ve ever seen data mysteriously revert to a previous state, this story is for you.

The Scene of the Crime: A Data Value That Wouldn’t Stick

I was working on an API endpoint. Part of its job was to fetch a “Part” entity from the database, increment a Counter property, and save it. Simple, right?

// Increment the counter and save it
part.Counter++;
dbContext.Parts.Update(part);
dbContext.SaveChanges();

This worked perfectly. I could look directly at the SQL database, and the Counter value was correctly updated. But later in the same endpoint’s execution, the Counter would be reset to its original value. Something was overwriting my changes.

I pinpointed the line that was causing the reset. It was another update to the same part entity, but for a completely different field.

// This unrelated update was resetting the Counter!
part.ModifiedAt = DateTime.UtcNow;
dbContext.Parts.Update(part);
dbContext.SaveChanges();

The Counter property wasn’t being touched anywhere else. So why was it being overwritten? My first thought was that the part object itself must have the wrong data. I logged its Counter value right before the problematic save.

Console.WriteLine(part.Counter); // Output: Wrong value

It was stale. The object in my code didn’t reflect the change I had just saved to the database. Baffled, I tried fetching a fresh copy of the entity from the database.

// Let's get a fresh instance
Parts part2 = dbContext.Parts.Where(x => x.Id == partId).First();
Console.WriteLine(part2.Counter); // Output: Still the wrong value!

This was maddening. I could see the correct, incremented value in the database with my own eyes, yet Entity Framework was handing me back an old version. As a final sanity check, I tried selecting only the Counter value, bypassing the full entity.

// Just select the value directly
int counter = dbContext.Parts.Where(x => x.Id == partId).Select(x => x.Counter).First();
Console.WriteLine(counter); // Output: Correct value!

Success! But this only deepened the mystery. Why do these two almost identical queries produce different results?

// This returns the OLD value from a tracked entity
dbContext.Parts.Where(x => x.Id == partId).First().Counter;

// This returns the NEW value directly from the database
dbContext.Parts.Where(x => x.Id == partId).Select(x => x.Counter).First();

The “Aha!” Moment: Identity Resolution and a Rogue DbContext

The answer lies in two key concepts: Entity Framework’s Identity Resolution and improper DbContext management.

What is Identity Resolution?

As explained in the Microsoft documentation, when Entity Framework retrieves an entity from the database, it keeps a reference to that object in its memory. This is called “tracking.” If you query for the same entity again within the same DbContext instance, EF won’t actually go back to the database. It will just hand you the instance it already has in memory.

This is a performance feature. It’s faster and prevents you from having multiple copies of the same logical entity floating around.

// part and part2 are the EXACT same object in memory
Parts part = dbContext.Parts.Where(x => x.Id == partId).First();
Parts part2 = dbContext.Parts.Where(x => x.Id == partId).First();

Console.WriteLine(part == part2); // True

This explains why my second query for the full entity returned the old data—I was just getting the original, stale object back. The second line of code from my mystery (.Select(x => x.Counter)) bypasses Identity Resolution because it’s not asking for the tracked entity; it’s a projection that asks for a single, primitive value, forcing a direct database query.

But this was only half the problem. Why was the tracked entity stale in the first place?

The Real Culprit: A Second, Hidden DbContext

The business logic for this endpoint was complex, so the original developer had moved some of it into a helper class. Crucially, instead of using Dependency Injection to receive the existing DbContext, the helper class created its own new instance.

new AppDbContext()

This was the smoking gun. My endpoint now had two completely separate DbContext instances, each with its own independent tracking and Identity Resolution cache.

Here’s a play-by-play of what was happening:

Step Action in Parent (API Endpoint) Value in Parent’s DbContext Action in Helper Class Value in Helper’s DbContext Value in Database
1 Start Counter: 0 Counter: 0
2 Instantiate Helper Counter: 0 (Helper created with new DbContext) Counter: 0 Counter: 0
3 Increment Counter Counter: 1 Counter: 0 Counter: 1
4 Call Helper Logic Counter: 1 Update Part (using its own stale part object) Counter: 0 Counter: 0
5 End Counter: 1 Counter: 0 Counter: 0

The parent context incremented the counter and saved it. But when the helper class was asked to update the ModifiedAt field, its DbContext fetched the entity. Since it had never seen this entity before, it queried the database, saw Counter: 1, and started tracking it. However, the part object it was told to update was the one from the parent, which still had Counter: 0 in its local, untracked state. When SaveChanges() was called in the helper, it saw the part object with Counter: 0 and dutifully overwrote the database value.

The Fix and The Lesson: Manage Your Context

The solution was simple: Use Dependency Injection.

Instead of the helper class creating a new DbContext, I passed the existing context from the parent into its constructor. This ensured that the entire operation, across both the parent endpoint and the helper class, shared a single DbContext. With a single source of truth for tracking, the bug vanished instantly.

This frustrating debug session was a powerful reminder of a core principle of working with Entity Framework:

  1. One Request, One Context: For a given unit of work (like a single API request), you should use a single instance of your DbContext.
  2. Embrace Dependency Injection: Let the framework manage the lifetime of your DbContext. It’s the cleanest, safest, and most standard way to ensure Rule #1.
  3. Understand Tracking: Know when EF is returning a tracked object versus hitting the database. If you only need to read data and don’t plan on updating it, use .AsNoTracking() to improve performance and avoid side effects like this.

Next time you find data that refuses to save, take a deep breath and ask yourself: “Do I know where my DbContext has been?” You might just save yourself an afternoon.