How .AsNoTracking() Silently Broke My EF Core Update, and What I Learned
Peter is a Technical writer who has specific interests in software and API documentation. He is also a back-end developer who loves to share his knowledge of programming concepts on his blog and for other publications.
I spent several days trying to figure out why Entity Framework Core wasn’t saving the correct value. I was assigning the right ID, calling SaveChangesAsync(), and still getting wrong data in my DB. Turns out, one innocent-looking line of code was sabotaging the entire operation: .AsNoTracking().
That line cost me hours of debugging, silent rage, existential questions, and several cups of fuel-powered coffee. If you've ever doubted your own logic because your code should work but doesn’t — this post is for you.
The Setup: A Seemingly Straightforward Flow
Here’s the scenario. I was working on a feature where we manage committee members. When a committee nominee gets approved, we:
Copy their details to a new
MOCCommitteeMemberentity.Save that entity to the database.
Assign the new member’s ID to a history object (
MOCCommitteeHistory) that tracks changes.Save everything.
Sounds easy enough, right?
Here’s what it looked like:
var newMOCCommitteeMember = new MOCCommitteeMember
{
// mapping logic here...
};
_context.MOCCommitteeMembers.Add(newMOCCommitteeMember);
change.MOCCommitteeMemberId = newMOCCommitteeMember.Id;
_context.MOCCommitteeHistories.Update(change);
await _context.SaveChangesAsync();
But here's what hit me like a slap from nowhere:
After saving, the newMOCCommitteeMember.Id was 56.
But when I went to fetch the history data, the MOCCommitteeMemberId was stuck at 54.
Wait… what? 😑
Chasing Shadows: The Bug That Lied in Silence
I combed through my logic again and again. The assignment was happening. The ID was valid. EF didn’t throw any errors. Yet the data wasn’t syncing. It was like assigning x = 2 and then x somehow being 7.
I was ready to blame EF, the database, the network — anything.
Until I traced the data loading back to this:
var query = _context.MOCCommitteeHistories
.Where(h => h.MOCCommitteeId == id)
.AsNoTracking()
.Include(h => h.CurrentNominee)
.Include(h => h.MOCCommitteeMember);
Ah. There it was. That quiet traitor: .AsNoTracking().
So What’s the Problem?
.AsNoTracking() tells EF Core:
"Don’t track this entity. I’m just looking, not touching."
It’s perfect when you’re fetching data for read-only purposes. But when you plan to modify the entity and save it later? That’s where things go south.
EF Core doesn’t know about your changes anymore. So when you later call Update(entity), EF shrugs and acts like:
“Who’s this entity again? New guy? Cool. Let’s just INSERT him.”
Which means:
Your update gets treated weird.
Primary keys behave oddly.
Foreign key assignments don’t reflect.
You lose your mind in the process.
🛠️ The Fix: Let EF Track
Once I removed the .AsNoTracking(), everything worked like magic:
var query = _context.MOCCommitteeHistories
.Where(h => h.MOCCommitteeId == id)
.Include(h => h.CurrentNominee)
.Include(h => h.MOCCommitteeMember);
var history = await query.FirstOrDefaultAsync();
var newMember = new MOCCommitteeMember { /* copy nominee data */ };
_context.MOCCommitteeMembers.Add(newMember);
await _context.SaveChangesAsync(); // ID assigned here
history.MOCCommitteeMemberId = newMember.Id;
_context.MOCCommitteeHistories.Update(history);
await _context.SaveChangesAsync(); // everything persisted correctly
Lesson learned. If you’re going to change the data later, don’t tell EF to ignore the data now.
Bonus Tips for Surviving EF Core’s Mood Swings
Only use
.AsNoTracking()when you’re absolutely sure you won’t modify the data.If you must use it, you need to manually reattach the entity:
_context.Attach(entity); _context.Entry(entity).State = EntityState.Modified;Always double-check that
Idvalues are assigned after callingSaveChangesAsync().Use
ChangeTracker.Entries()to see what EF is tracking before and after save. It's like peeking behind the curtain.
Final Thoughts
This bug taught me more than just a lesson about EF Core.
It taught me to:
Pay attention to small things that look harmless
Read between the lines of my code
Trust my logic but verify the behavior of tools
So if you’re battling silent bugs and unexplained behavior in EF Core, check if you’ve accidentally ghosted EF with .AsNoTracking().
Sometimes, the real betrayal isn’t your logic; it’s the innocent line you forgot was even there.


