Skip to main content

Fixed: EF Core Returns the Old Value After ExecuteUpdateAsync

· 4 min read
Jagdish Kumawat
Founder @ Dewiride

I raised a value with ExecuteUpdateAsync, and read the product again, and got the old value back. The database had the new one. FirstAsync had gone to MySQL, read the new value, and then handed me the object it was already tracking, unchanged.

What I Was Doing​

In my .NET C# Web API, with EF Core 10.0.14, one endpoint loaded a product, changed it with ExecuteUpdateAsync, and read it back a few different ways:

An endpoint
g.MapPost("/stale/{id:int}", async (int id, InventoryDbContext db, CancellationToken cancellationToken) =>
{
var product = await db.Products.FirstAsync(p => p.Id == id, cancellationToken);
var before = product.ReorderLevel;

await db.Products
.Where(p => p.Id == id)
.ExecuteUpdateAsync(s => s.SetProperty(p => p.ReorderLevel, p => p.ReorderLevel + 1), cancellationToken);

var tracked = product.ReorderLevel;
var requeried = (await db.Products.FirstAsync(p => p.Id == id, cancellationToken)).ReorderLevel;
var projected = await db.Products.Where(p => p.Id == id).Select(p => p.ReorderLevel).FirstAsync(cancellationToken);

await db.Entry(product).ReloadAsync(cancellationToken);
var reloaded = product.ReorderLevel;

return Results.Ok(new { before, tracked, requeried, projected, reloaded });
});

What I Expected​

14 everywhere after the update.

What I Got​

Output
{
"before": 13,
"tracked": 13,
"requeried": 13,
"projected": 14,
"reloaded": 14
}

A JSON response with before 13, tracked 13, requeried 13, projected 14 and reloaded 14.

tracked and requeried are still 13. projected and reloaded are 14.

What I Checked​

The docs. The ExecuteUpdate page says of ExecuteUpdate and ExecuteDelete: "In fact, the functions are completely unaware of EF's change tracker, and have no interaction with it whatsoever."

Why requeried is stale, though it's a new query. That query did go to the database. But it returned a whole entity, the context was already tracking that entity, and EF Core gives you the object it's tracking and keeps its values.

Why projected is right. It selects a single column, so there's no entity to reuse, and the value comes straight from the query.

Why It Happens​

ExecuteUpdateAsync sends SQL directly and returns a row count. The objects in memory don't know about it. Anything that returns the tracked object shows what that object already held.

The Fix​

Choose whichever suits the code:

You wantDo this
One valueProject it: .Select(p => p.ReorderLevel).FirstAsync()
The entity you're holding, refreshedawait db.Entry(product).ReloadAsync()
Nothing left over from beforedb.ChangeTracker.Clear(), and then query
No tracking in this read at all.AsNoTracking()

Better still, don't load the entity first if all you need is the update. The code that follows can then only see fresh values.

After the Fix​

The last two values in the same response are those two fixes working: "projected":14 from the projection, and "reloaded":14 from ReloadAsync.

An endpoint (excerpt)
await db.Entry(product).ReloadAsync(cancellationToken);
var reloaded = product.ReorderLevel;

The same JSON response, with projected and reloaded both 14.

Common Mistakes​

MistakeWhat happensDo this instead
Calling SaveChangesAsync on the stale entityIt can overwrite the new valueReload first, or don't mix them
Assuming a new query means new objectsTracked entities come back as they wereProject, or use AsNoTracking
Returning the tracked entity to the clientThe client gets the old valueRead the value after the update

Troubleshooting​

SymptomLikely causeFix
The API returns the old value and the database has the new oneThisProject the new value
ExecuteUpdateAsync returns 0No row matchedCheck the WHERE
The new value disappears laterSaveChanges wrote the old one backDon't mix the two on one entity

Conclusion​

ExecuteUpdateAsync doesn't touch the change tracker, so a tracked entity keeps its old values, and even a new query hands that entity back. Project the value, reload the entity, or clear the tracker.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.