Instance of Entity Type Cannot Be Tracked: EF Core Fixes

Last updated: November 1, 2026 · Tested with .NET 10.0.12 (SDK 10.0.401), EF Core 10.0.14 with SQLite, Windows 11

Short answer: a DbContext can track only one object per primary key. EF Core throws this error when a second object with the same key reaches that context, usually because you loaded a row and then called Update() with a new object. Edit the object EF Core already tracks instead. Don’t reach for Detach or ChangeTracker.Clear() first: in my tests both removed the error and silently dropped other changes.

The entity name in the message tells you where to look. If you were updating a Product and the message says Category, the problem is in the object graph, not in your update.

What you see Cause Fix
Error on Update() after a load in the same method Loaded object and new object share a key Edit the loaded object, or CurrentValues.SetValues
Every PUT returns 500, the handler looks fine A filter or validator loaded the row first AnyAsync or AsNoTracking in the check
First request works, second one fails DbContext registered as a singleton Scoped context, or IDbContextFactory
Message names a related entity Two copies of one parent in the graph Null the navigation and keep the foreign key
Error after an AsNoTracking query No-tracking results repeat parents as separate objects AsNoTrackingWithIdentityResolution
Error on the second Add() Non-generated key left at 0 Set the key before Add
Error after a record with expression The copy is a new object with the same key Use a class, or copy values with SetValues
Passes alone, fails inside a test Arrange and Act share one context ChangeTracker.Clear() after Arrange

The Error

The exact message on EF Core 10:

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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

It doesn’t say which row. Turn on EnableSensitiveDataLogging in development and you get the key value, which helps a lot when the error comes from a list of 500 items:

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

Keep that option out of production, because it also puts parameter values into your logs.

With a composite key the message lists every key column:

InvalidOperationException: The instance of entity type 'OrderLine' cannot be tracked because another instance with the same key value for {'OrderId', 'LineNo'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

How I Tested It

One project with 28 console scenarios and five versions of a small minimal API, called over real HTTP. Each scenario starts from a fresh SQLite database with two products, one category, one order with two lines and one customer. Db.Open() returns a new ShopDb with SQL logging on. Every output below is copied from the run. I tested on SQLite only; the error comes from the change tracker before any SQL runs, so it shouldn’t depend on the provider, but the SQL text and the foreign key error in the fixes section will look different on SQL Server.

Why It Happens

EF Core keeps one object per key in each context, which the docs call identity resolution. This test shows it at work:

db.Products.Find(1); Console.WriteLine($"commands after first Find: {Db.Sql.Count}");
db.Products.Find(1); Console.WriteLine($"commands after second Find: {Db.Sql.Count}");
var f = db.Products.First(p => p.Id == 1); Console.WriteLine($"commands after First: {Db.Sql.Count}");
Console.WriteLine($"same instance: {ReferenceEquals(f, db.Products.Find(1))}");
commands after first Find: 1
commands after second Find: 1
commands after First: 2
same instance: True

The second Find ran no SQL, and First ran a query but returned the object already in memory. If two different objects with the same key were allowed in, EF Core couldn’t know which values to save, so it refuses.

Cause 1: Load, Then Update a New Object

This is the classic version:

var existing = db.Products.Find(1);
db.Products.Update(new Product { Id = 1, Name = "Keyboard Pro", Price = 60m, CategoryId = 1 });

The call to Update throws straight away, not at SaveChanges. I also tried the other ways in, and all of them throw the same exception when a different object has that key:

Attach: InvalidOperationException
Add: InvalidOperationException
Remove: InvalidOperationException
Update/Attach of the SAME instance: no error

db.Entry(obj).State = EntityState.Modified throws it too. Passing the same object again is fine, because the check compares objects, not values.

Cause 2: A Filter Loaded the Row First

This one is harder to spot, because the handler has no load in it. The handler on its own works:

group.MapPut("/{id:int}", async (int id, ProductDto dto, ShopDb db) =>
{
    db.Products.Update(new Product { Id = id, Name = dto.Name, Price = dto.Price, CategoryId = 1 });
    await db.SaveChangesAsync();
    return Results.NoContent();
});

Now add an existence check in an endpoint filter. The filter gets the same scoped ShopDb as the handler:

var product = await db.Products.FirstOrDefaultAsync(p => p.Id == id);
if (product is null) return Results.NotFound();
return await next(ctx);
PUT 1: 500 InvalidOperationException: The instance of entity type 'Product' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. W...
PUT 2: 500 InvalidOperationException: The instance of entity type 'Product' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. W...

The same applies to anything that shares the request’s context: a validator, a middleware, an authorization handler, a service called before the update. An existence check doesn’t need the entity, so ask only for that:

var exists = await db.Products.AnyAsync(p => p.Id == id);
if (!exists) return Results.NotFound();
return await next(ctx);
PUT 1: 204
PUT 2: 204

I checked which reads leave something in the tracker. Any, a projection to plain values and AsNoTracking left nothing. A projection that includes the entity itself still tracks it:

var ent = db.Products.Where(p => p.Id == 1).Select(p => new { p, p.Name }).First();
tracked after Any + projection + AsNoTracking: 0
tracked after projection that contains the entity: 1

Cause 3: A Singleton DbContext

Same handler, no filter, one change in registration:

builder.Services.AddDbContext<ShopDb>(o => o.UseSqlite(Db.Cs), ServiceLifetime.Singleton);
PUT 1: 204
PUT 2: 500 InvalidOperationException: The instance of entity type 'Product' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. W...

The first request succeeds, and the object stays tracked after the save. Any later request for the same id fails, so this looks random in production: a smoke test passes, then users hit the same row twice. Remove the ServiceLifetime.Singleton argument, because the default is scoped.

If a singleton service or a background worker needs the database, give it IDbContextFactory and create a short-lived context per operation:

builder.Services.AddDbContextFactory<ShopDb>(o => o.UseSqlite(Db.Cs));
builder.Services.AddSingleton<ProductWriter>();
public class ProductWriter(IDbContextFactory<ShopDb> factory)
{
    public async Task SaveAsync(int id, ProductDto dto)
    {
        await using var db = await factory.CreateDbContextAsync();
        db.Products.Update(new Product { Id = id, Name = dto.Name, Price = dto.Price, CategoryId = 1 });
        await db.SaveChangesAsync();
    }
}
PUT 1: 204
PUT 2: 204

Lifetime mistakes like this one are covered in more detail in the C# dependency injection guide.

Cause 4: Two Copies of a Parent in the JSON

Here nothing was loaded at all. A client posts two products, and each one carries its own copy of category 1:

[{"id":1,"name":"K","price":1,"categoryId":1,"category":{"id":1,"name":"Books"}},
{"id":2,"name":"M","price":2,"categoryId":1,"category":{"id":1,"name":"Books"}}]
var items = JsonSerializer.Deserialize<List<Product>>(graphJson, JsonSerializerOptions.Web)!;
using var db = Db.Open();
db.Products.UpdateRange(items);
InvalidOperationException: The instance of entity type 'Category' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

The deserializer made two Category objects with Id 1, and Update walks the whole graph. The simplest fix is to drop the navigation, because CategoryId already holds the link:

items.ForEach(i => i.Category = null);
using var db = Db.Open();
db.Products.UpdateRange(items);
Console.WriteLine($"rows: {db.SaveChanges()}");
rows: 2

If the graph has to stay, walk it with TrackGraph and skip parents that are already tracked. This also wrote 2 rows, and the category wasn’t updated:

foreach (var item in items)
    db.ChangeTracker.TrackGraph(item, n =>
    {
        if (n.Entry.Entity is Category c && db.Categories.Local.Any(x => x.Id == c.Id))
            return;
        n.Entry.State = n.Entry.Entity is Product ? EntityState.Modified : EntityState.Unchanged;
    });

Better still, stop accepting entities in your API. A DTO with only CategoryId can’t cause this.

Cause 5: AsNoTracking Duplicates Parents

Many answers say to add AsNoTracking(). It fixes Cause 2, and it causes this one. Without tracking, EF Core doesn’t merge repeated rows, so each product gets its own Category object:

var list = db.Products.AsNoTracking().Include(p => p.Category).ToList();
Console.WriteLine($"same Category object: {ReferenceEquals(list[0].Category, list[1].Category)}");
db.Products.UpdateRange(list);
same Category object: False
InvalidOperationException: The instance of entity type 'Category' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Use the variant that resolves identity for the query without tracking the results:

var list = db.Products.AsNoTrackingWithIdentityResolution().Include(p => p.Category).ToList();
same Category object: True
rows: 3

Note the 3: UpdateRange also marked the category as modified and rewrote it. On a read-then-update path, loading with tracking is usually the better choice.

Cause 6: A Non-Generated Key Left at 0

This one throws on a plain Add, with no existing row anywhere:

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
db.Invoices.Add(new Invoice { Number = "A" });
db.Invoices.Add(new Invoice { Number = "B" });
InvalidOperationException: The instance of entity type 'Invoice' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Both invoices have Id 0, and 0 is a real key here. With a generated key the same code saves 2 rows, because EF Core gives each object a temporary value. If you see this in a loop, check whether the key is really being set.

Cause 7: Records and the with Expression

var c = db.Customers.Find(1)!;
var changed = c with { Name = "Anna" };
db.Update(changed);
InvalidOperationException: The instance of entity type 'Customer' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

with always creates a new object, so it’s Cause 1 in one line. Records fit DTOs well; use classes for entities you update.

Cause 8: One Context in a Test

A test that seeds through the same context it then exercises hits Cause 1 by accident. Here ChangeTracker.Clear() is the right tool, because Arrange has already saved everything:

db.Products.Add(new Product { Id = 50, Name = "Seed", CategoryId = 1 });
db.SaveChanges();
db.ChangeTracker.Clear();
db.Products.Update(new Product { Id = 50, Name = "Act", CategoryId = 1 });
without Clear: InvalidOperationException
with Clear after Arrange, rows: 1

The Fixes, Compared

Edit the tracked object. The best default. Only the changed column is written:

var p = db.Products.Find(1)!;
p.Price = 60m;
UPDATE "Products" SET "Price" = @p0 WHERE "Id" = @p1 RETURNING 1;

SetValues from a DTO. The best choice when the new values arrive as an object. It copies matching properties, and EF Core works out what really changed. I passed the same Name and a new Price, and only Price was written:

db.Entry(p).CurrentValues.SetValues(new { Name = "Keyboard", Price = 60m });
state: Modified, rows: 1
UPDATE "Products" SET "Price" = @p0 WHERE "Id" = @p1 RETURNING 1;
Id=1 Name=Keyboard Price=60.0 CategoryId=1

Pass a DTO, not a half-filled entity. A new Product brings every property with it, including a CategoryId of 0:

db.Entry(p).CurrentValues.SetValues(new Product { Id = 1, Name = "Keyboard", Price = 60m });
DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
inner: SQLite Error 19: 'FOREIGN KEY constraint failed'.

That one failed loudly. A column without a foreign key would simply have been overwritten.

Check Local first. Useful in a generic repository, where you don’t know what was loaded earlier:

var local = db.Products.Local.FirstOrDefault(p => p.Id == incoming.Id);
if (local is not null) db.Entry(local).CurrentValues.SetValues(incoming);
else db.Products.Update(incoming);

ExecuteUpdate. It skips the tracker completely, so it can’t throw this error and it needs no load. The catch is that a tracked object in the same context goes stale:

var rows = db.Products.Where(p => p.Id == 1).ExecuteUpdate(s => s.SetProperty(p => p.Price, 60m));
tracked instance still says Price=50.0
database says Id=1 Name=Keyboard Price=60.0 CategoryId=1

What Update() Does to Fields You Left Out

Once the error is gone, check what Update writes. On a detached object it can’t know what changed, so it sends every column. Product 2 cost 25 before this:

db.Products.Update(new Product { Id = 2, Name = "Mouse v2", CategoryId = 1 });
UPDATE "Products" SET "CategoryId" = @p0, "Name" = @p1, "Price" = @p2 WHERE "Id" = @p3 RETURNING 1;
Id=2 Name=Mouse v2 Price=0.0 CategoryId=1

No exception, and the price is now 0. This is why I prefer SetValues from a DTO to the common “load with AsNoTracking, then Update” advice. That advice removes the error but keeps this problem.

It also fails differently when the row is gone. Update doesn’t check first, so a missing id only shows up at SaveChanges:

DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

Why I Avoid Detach and Clear

Detaching is a top suggestion when you search this error. It works, with three traps I reproduced.

It drops edits on that object. The price was changed to 99 before the detach, and the saved row has 50:

var p = db.Products.Find(1)!;
p.Price = 99m;
db.Entry(p).State = EntityState.Detached;
db.Products.Update(new Product { Id = 1, Name = "Keyboard Pro", Price = 50m, CategoryId = 1 });
rows: 1
Id=1 Name=Keyboard Pro Price=50.0 CategoryId=1

It doesn’t reach parents. I expected children to stay behind as well, but an order’s required lines were detached with it (still tracked: 0). A parent loaded through Include does stay, so the error comes back under a new name:

var p = db.Products.Include(x => x.Category).First(x => x.Id == 1);
db.Entry(p).State = EntityState.Detached;
still tracked: Category
InvalidOperationException: The instance of entity type 'Category' 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. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Clear() drops everything. The context had a new product and a price edit waiting. After Clear, neither was saved, and nothing warned about it:

pending before Clear: 2
rows: 1
Pending saved: False; Id=2 Name=Mouse Price=25.0 CategoryId=1

In a request that does more than one thing, that is lost data with a 200 response.

Don’t Turn Tracking Off Globally

UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) also makes the error go away, along with every normal update:

var p = db.Products.First(x => x.Id == 1);
p.Price = 99m;
Console.WriteLine($"rows: {db.SaveChanges()}");
rows: 0

No exception and no write. Even Find doesn’t track on that context. Use it only for a read-only context.

Checklist

  • Read the entity name in the message. A parent entity means a graph problem.
  • Add EnableSensitiveDataLogging in development to get the conflicting key.
  • Find what loaded the row: a filter, a validator, a service, or an earlier line.
  • Check that the DbContext is scoped, not a singleton.
  • For updates, load with tracking and use CurrentValues.SetValues from a DTO.
  • For existence checks, use AnyAsync.
  • Don’t use Detach or ChangeTracker.Clear() in production paths; keep Clear for tests.

If this context is shared by parallel tasks, you’ll meet its neighbour, “A second operation was started on this context”; the repository and unit of work guide covers how to keep one context per unit of work.

Related: EF Core PendingModelChangesWarning · EF Core 11 breaking changes · EF Core tutorial: migrations and performance

Similar Posts

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *