Skip to main content

Command Palette

Search for a command to run...

The Entity Instance Cannot Be Tracked in ASP.NET Core: Causes and Fixes

Updated
9 min readView as Markdown
The Entity Instance Cannot Be Tracked in ASP.NET Core: Causes and Fixes

If you have hit System.InvalidOperationException: The instance of entity type 'X' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked, you are looking at one of the most common EF Core errors in real ASP.NET Core update endpoints. It almost always means your DbContext is tracking two different objects that claim to be the same database row, and EF Core refuses to guess which one wins. By Celin Daniel, Co-founder of Coding Droplets, with 13+ years building .NET in production.

I have debugged this exact exception more times than I would like to admit, usually in a PUT handler that loads an entity and then tries to save a mapped copy. The fix is almost never the one people reach for first (clearing the tracker), and the wrong fix quietly creates new bugs. If you want the complete, runnable update patterns - load-and-mutate, SetValues, and disconnected graphs - wired into a working API with tests, the full version is on Patreon, ready to clone and run.

The root cause is almost always a change-tracking misunderstanding rather than a one-line bug, so the durable fix is getting the update pattern right. That is exactly what Chapter 3 of the Zero to Production course covers - AsNoTracking, FindAsync versus FirstOrDefaultAsync, and the change tracker - inside a complete ASP.NET Core API you can run, so the behavior is never abstract.

ASP.NET Core Web API: Zero to Production

What Does "Cannot Be Tracked Because Another Instance Is Already Being Tracked" Mean?

EF Core's change tracker holds at most one tracked instance per entity type per primary key. The error fires the moment you ask it to track a second object with a key it is already tracking. In short:

  • EF Core identifies a tracked entity by its type plus primary key, not by object reference.

  • If two different objects share that identity, the tracker cannot reconcile them, so it throws instead of silently picking one.

  • It is raised at Attach, Update, Add, or the moment a query result would introduce the duplicate - not at SaveChangesAsync.

Here is the full message you will see in the logs:

System.InvalidOperationException: The instance of entity type 'Product' cannot be tracked
because another instance with the same key value for {'Id'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key
value is attached.

The Most Common Causes

In ASP.NET Core APIs, this error comes from a short list of situations:

  1. Loading an entity, then calling Update() on a mapped copy with the same Id (the classic PUT endpoint bug).

  2. Attaching a detached graph where a child entity shares a key with something already tracked.

  3. A DbContext that lives too long and accumulates tracked entities across requests.

  4. The same entity appearing twice in one graph you pass to Update or AddRange.

The fixes below map directly to these causes.

Fix 1: Modify the Tracked Entity Instead of Attaching a Copy

This is the cause I see most. The endpoint reads the row (now tracked), maps the incoming DTO to a brand new entity with the same Id, and calls Update:

// Broken: 'existing' is tracked, then a second instance with the same Id is attached
var existing = await _db.Products.FirstOrDefaultAsync(p => p.Id == id);
var updated = new Product { Id = id, Name = dto.Name, Price = dto.Price };
_db.Products.Update(updated);   // throws here
await _db.SaveChangesAsync();

The fix is to stop creating a second instance. Mutate the entity EF Core already tracks and let it detect the change - no Update() call needed at all:

// Correct: change the tracked instance directly
var existing = await _db.Products.FirstOrDefaultAsync(p => p.Id == id);
if (existing is null) return NotFound();

existing.Name = dto.Name;
existing.Price = dto.Price;
await _db.SaveChangesAsync();   // EF Core writes only the changed columns

This is simpler, and it generates a tighter UPDATE statement because EF Core knows exactly which properties changed.

Fix 2: Use SetValues for Disconnected Updates

When you have a DTO with many fields and do not want to assign each one by hand, copy the values onto the tracked entry. CurrentValues.SetValues maps matching property names for you:

var existing = await _db.Products.FindAsync(id);
if (existing is null) return NotFound();

_db.Entry(existing).CurrentValues.SetValues(dto);
await _db.SaveChangesAsync();

FindAsync is deliberate here: if the entity is already tracked, it returns the tracked instance without a database round trip, which sidesteps the duplicate entirely.

Fix 3: Read With AsNoTracking When You Plan to Attach

Sometimes you genuinely want the disconnected pattern: read the entity (perhaps to authorize the caller), then attach a separately built instance. In that case the read must not track, or you will have two tracked instances again:

var current = await _db.Products
    .AsNoTracking()                       // read stays untracked
    .FirstOrDefaultAsync(p => p.Id == id);
if (current is null) return NotFound();

var updated = new Product { Id = id, Name = dto.Name, Price = dto.Price };
_db.Products.Update(updated);             // safe: nothing with this key is tracked
await _db.SaveChangesAsync();

Using AsNoTracking on read queries is good practice well beyond this error - it is one of the first things I check when a read endpoint is slow. The same change-tracker confusion is behind a related production bug I wrote up in EF Core returns stale data after update.

Fix 4: Keep DbContext Scoped, Never Singleton

If this error appears intermittently and seems tied to load rather than a specific request, suspect DbContext lifetime. A DbContext is meant to live for a single request. Capture it in a singleton or a long-lived field and it keeps tracking entities from earlier requests, so eventually a new query collides with a stale tracked instance.

The default AddDbContext registration is scoped, which is correct. The trap is injecting a scoped DbContext into a singleton (including a BackgroundService); resolve a fresh scope per unit of work instead. If you are unsure which registration you need, AddDbContext vs AddDbContextPool vs AddDbContextFactory breaks down the trade-offs, and a captured context is also a frequent source of the ObjectDisposedException on DbContext.

Fix 5: Clear the Change Tracker for Batch Scenarios (Use With Care)

For genuine batch work - processing thousands of disconnected entities in a loop - clearing the tracker between units is legitimate:

_db.ChangeTracker.Clear();   // EF Core 5+; detaches everything currently tracked

But reaching for ChangeTracker.Clear() to silence a failing PUT endpoint is treating the symptom. If a single request tracks two instances of one row, the update pattern is wrong - fix that instead. Clearing the tracker mid-request can detach entities you still need and lead to lost updates.

How to Avoid It in the First Place

A few habits make this error nearly disappear:

  • Prefer load-and-mutate over attach-a-copy for single-entity updates. It is less code and produces better SQL.

  • Use AsNoTracking on every read that does not feed a write in the same request.

  • Keep one unit of work per request, with a scoped DbContext and no long-lived captures.

  • Map onto the tracked entity with SetValues rather than constructing parallel instances.

For the official deep dive, Microsoft's guide on disconnected entities and change tracking is worth a read. These notes apply to EF Core through the current EF Core 10 on .NET 10.

Frequently Asked Questions

Why does EF Core say the instance cannot be tracked when I only have one object?

You almost certainly have two. EF Core tracks entities by type plus primary key, so a row you loaded earlier in the request and a new object you built with the same Id count as two instances of the same identity. The tracker already holds the first, so attaching the second throws.

How do I fix "another instance with the same key value is already being tracked" in a PUT endpoint?

Load the entity, change its properties directly, and call SaveChangesAsync without Update(). Because the loaded entity is already tracked, EF Core detects the modified properties and writes them. This avoids ever introducing a second instance with the same key.

Does AsNoTracking prevent this error?

It prevents the variant where a read tracks an entity you later attach a copy of. If you read with AsNoTracking, the read does not enter the change tracker, so attaching a separately built instance with the same key is safe. It does not help if the duplicate comes from a long-lived DbContext tracking entities across requests.

Is calling ChangeTracker.Clear() a good fix for this error?

Only for batch processing of disconnected entities. Inside a normal request it is a workaround that hides a wrong update pattern and can cause lost updates by detaching entities you still need. Fix the update flow instead of clearing the tracker.

Can a singleton or background service cause this EF Core tracking error?

Yes. Injecting a scoped DbContext into a singleton, or holding a DbContext in a long-lived field, keeps it tracking entities from earlier work. A later query then collides with a stale tracked instance. Resolve a fresh scope per unit of work with IServiceScopeFactory.


About the Author

I'm Celin Daniel, Co-founder of Coding Droplets. I've been building .NET and ASP.NET Core systems in production for 13+ years - APIs, distributed backends, enterprise platforms. Everything I write here comes from real shipping experience: patterns that held up, trade-offs that bit us, and lessons learned the hard way.

More from this blog

C

Coding Droplets

276 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.