EF Core ExecuteUpdate

ExecuteUpdateAsync() updates rows directly in the database without loading entities into memory, without tracking them, and without calling SaveChangesAsync().

It is useful when you want to update one or more properties (columns) for all rows that match a query.

⚡ Bulk Insert Related Entities Automatically

Saving Orders with OrderItems? Use IncludeGraph to bulk insert entire entity graphs while handling their relationships automatically.

Discover IncludeGraph →

Update Rows with a Filter

Most of the time, you will combine Where() with ExecuteUpdateAsync().

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsVisible, false));

Executed SQL:

language-sql
|
UPDATE [b]
    SET [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Only rows that match the filter are updated.

Update All Rows

If you call ExecuteUpdateAsync() directly from a DbSet, EF Core updates every row in that table.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsVisible, true));

This executes a database UPDATE for all Blogs.

language-sql
|
UPDATE [b]
    SET [b].[IsVisible] = CAST(1 AS bit)
FROM [Blogs] AS [b]

Use this carefully because no filter is applied.

Update One Property

Use SetProperty() to specify the property you want to update.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Name == "ZZZ Projects")
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, 5));

This updates only the Rating property for the matching blog.

Update Multiple Properties

You can chain multiple SetProperty() calls to update more than one property.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, 3)
        .SetProperty(b => b.IsVisible, false));

This updates both Rating and IsVisible in the same database command.

Update Using the Current Value

The new value can also be based on the current database value.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.IsVisible)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, blog => blog.Rating + 1));

This increments the current Rating value for all visible blogs.

The expression must be translatable to SQL.

Update with a Navigation Filter

You can use navigation data in the query filter before calling ExecuteUpdateAsync().

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Posts.All(p => p.PublishedOn.Year < 2018))
    .ExecuteUpdateAsync(s => s
        .SetProperty(post => post.IsVisible, false));

This updates all blogs that contain only posts published before 2018.

The filter is translated to SQL and executed directly in the database.

Update using a regular lambda

Starting in EF Core 10, ExecuteUpdateAsync accepts a regular lambda, not only expression trees.

This makes dynamic/conditional updates easier to write, as long as every SetProperty remains SQL-translatable.

language-csharp
|
bool nameChanged = true; // Local variable (not a column)
await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s =>
{
    s.SetProperty(b => b.Views, 8);
    if (nameChanged)
    {
        s.SetProperty(b => b.Name, "foo");
    }
});

Rows Affected

ExecuteUpdateAsync() returns an int value that indicates the number of rows updated.

language-csharp
|
using var context = new AppDbContext();

var rowsAffected = await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsVisible, false));

You can use this value for logging, validation, or checking whether the update matched the expected rows.

Important Behavior

ExecuteUpdateAsync() executes immediately.

That means:

  • it does not require SaveChangesAsync()
  • it does not use the ChangeTracker
  • it does not update tracked entity instances already loaded in memory
  • it sends an UPDATE command directly to the database

For tracked entity workflows, see SaveChanges.

For a deeper step-by-step explanation of what EF Core does internally when changes are saved through the normal tracked workflow, see How SaveChanges Works.

For a deeper explanation of tracking behavior, see ChangeTracker.

ExecuteUpdate Requirements

ExecuteUpdateAsync() is available starting with EF Core 7.

(For older versions of EF Core (EF Core 2+) or if you prefer the syntax provided by EF Extensions over using SetProperty, you can use UpdateFromQuery.)

To use it correctly, keep these requirements in mind:

  • the update must be based on an EF Core query
  • the values passed to SetProperty() must be translatable to SQL
  • the operation runs immediately in the database
  • tracked entities already loaded in the current DbContext are not automatically updated in memory
  • SaveChangesAsync() is not required after calling ExecuteUpdateAsync()

For example, this works because the expression can be translated to SQL:

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Rating < 5)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, b => b.Rating + 1));

This increments the Rating column directly in the database.

However, custom .NET methods that cannot be translated to SQL cannot be used inside SetProperty().

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Name, b => NormalizeName(b.Name)));

This kind of expression cannot be translated unless NormalizeName() is mapped or translated by the provider.

How ExecuteUpdate Works

ExecuteUpdateAsync() sends an UPDATE command directly to the database.

It does not follow the normal tracked entity workflow.

A normal tracked update usually looks like this:

language-csharp
|
using var context = new AppDbContext();

var blog = await context.Blogs
    .FirstAsync(b => b.Name == "ZZZ Projects");

blog.Rating = 5;

await context.SaveChangesAsync();

In that workflow, EF Core:

  1. loads the entity
  2. tracks it in the DbContext
  3. detects the property change
  4. generates the update when SaveChangesAsync() runs

ExecuteUpdateAsync() skips that workflow.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Name == "ZZZ Projects")
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, 5));

In this case, EF Core translates the query and update expression into a direct database command.

No entity instance is loaded, and the ChangeTracker is not involved.

ExecuteUpdate vs SaveChanges

ExecuteUpdateAsync() and SaveChangesAsync() both update data, but they are designed for different workflows.

Feature ExecuteUpdateAsync() SaveChangesAsync()
Loads entities No Usually yes
Uses ChangeTracker No Yes
Requires SaveChangesAsync() No It is the save operation
Best for Set-based updates Tracked entity workflows
Updates in-memory tracked entities No Yes
Can use per-entity business logic in memory No Yes

Use ExecuteUpdateAsync() when you want to update rows directly in the database.

Use SaveChangesAsync() when you are working with tracked entities and want EF Core to detect changes during a unit of work.

For the tracked entity workflow, see SaveChanges.

For tracking behavior, see ChangeTracker.

ExecuteUpdate vs BulkUpdate

Is ExecuteUpdate a real “Bulk Update”? Not quite. Yes, it updates rows in bulk directly in SQL. However, it does not bulk update entities with their own per-row values like BulkUpdate (Entity Framework Extensions).

ExecuteUpdateAsync() is a set-based update API built into EF Core.

It is useful when every matching row can be updated using the same SQL-translatable rule.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsVisible, false));

This works well because the update is expressed as one database rule.

However, ExecuteUpdateAsync() is not the same as a full bulk update library.

Use BulkUpdate from Entity Framework Extensions when:

  • each row has different values coming from memory
  • you need to update many entities from a list
  • you need advanced options such as batching, auditing, custom mappings, or output values
  • you need more control over large data synchronization scenarios

In short:

  • use ExecuteUpdateAsync() for set-based updates expressed as a query
  • use BulkUpdate when each row needs different values from entities in memory
Question ExecuteUpdateAsync() BulkUpdate (EF Extensions)
How are values applied? One SQL rule for all matching rows Values come from each entity
Where does the data come from? Query expression translated to SQL In-memory entity list
Different values per row? No Yes
Uses ChangeTracker? No Not the normal EF Core tracking workflow
Best for Set-based updates High-volume updates with per-row values

Performance Benchmarks

Updating 100,000 rows (3 int columns + 3 string columns):

Method Time Memory
ExecuteUpdate 365 ms Very low
BulkUpdate (EF Extensions) 1900 ms Low
SaveChanges 4800 ms High

Benchmark Source

Important: For BulkUpdate and SaveChanges, part of the time is spent materializing the list of entities from the database before the update starts. This step is required because BulkUpdate and SaveChanges work from entities, not from a LINQ rule.

Takeaway: ExecuteUpdate can be much faster than the traditional tracked approach when the update can be expressed as a set-based SQL rule. BulkUpdate remains more appropriate when each row needs different values coming from entities in memory.

Bulk Update - Concrete Example

ExecuteUpdateAsync() works well when the same SQL rule applies to every matching row.

A real bulk update from EF Extensions is different: each entity can carry its own value from memory.

For example, imagine you need to update 1,000,000 products, and each product already has a different NewPrice value calculated in memory.

language-csharp
|
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;

var products = GetMillionProductsWithNewPrices();

context.BulkUpdate(products, options =>
{
    options.ColumnInputExpression = p => new { p.Id, p.NewPrice };
    options.BatchSize = 10000;
});

Doing the same with ExecuteUpdateAsync() in bulk is impossible.

ExecuteUpdateAsync() and BulkUpdate complement each other rather than compete.

🔎 Query Thousands of IDs Efficiently

Working with a large list of IDs or composite keys? WhereBulkContains helps you filter database records using large in-memory collections.

Discover WhereBulkContains →

When to Use ExecuteUpdate

Use ExecuteUpdateAsync() when:

  • you want to update rows directly in the database
  • the update can be expressed as a SQL-translatable rule
  • you do not need to load entities first
  • you do not need the ChangeTracker
  • you do not need entity instances updated in memory
  • you want to update many rows with one database command

Good examples include:

  • hiding old records
  • increasing a numeric value
  • setting a status for all rows that match a filter
  • updating a flag based on related data
  • applying a simple batch correction
language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.LastUpdated < cutoffDate)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsArchived, true));

When Not to Use ExecuteUpdate

ExecuteUpdateAsync() is not the best choice when the update depends on tracked entity behavior.

Avoid it when:

  • you need to update already loaded entity instances in memory
  • you rely on ChangeTracker state
  • you need entity events or in-memory business logic before saving
  • each row needs a different value from a local object list
  • the update expression cannot be translated to SQL
  • you need the normal SaveChangesAsync() pipeline

For example, if your application loads an entity, applies domain logic, validates it, and then saves it, a tracked workflow is usually clearer.

language-csharp
|
using var context = new AppDbContext();

var blog = await context.Blogs
    .FirstAsync(b => b.Name == "ZZZ Projects");

blog.Rating = 5;

await context.SaveChangesAsync();

For normal tracked updates, see Updating Data.

Common Pitfalls

Be careful with the following mistakes.

Expecting SaveChangesAsync to Be Required

ExecuteUpdateAsync() runs immediately.

This is unnecessary:

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.IsVisible, false));

// DO NOT USE!
await context.SaveChangesAsync();

The SaveChangesAsync() call does not save the ExecuteUpdateAsync() operation. The update already happened.

Expecting Tracked Entities to Be Updated in Memory

If an entity is already tracked, ExecuteUpdateAsync() does not automatically update that in-memory instance.

language-csharp
|
using var context = new AppDbContext();

var blog = await context.Blogs
    .FirstAsync(blog => blog.Name == "ZZZ Projects");

await context.Blogs
    .Where(b => b.Name == "ZZZ Projects")
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Rating, 5));

// The tracked blog instance may still have the old Rating value.

If you need fresh values, reload the entity or use a new DbContext.

Using Non-Translatable Expressions

The update expression must be translatable to SQL.

language-csharp
|
using var context = new AppDbContext();

await context.Blogs
    .ExecuteUpdateAsync(s => s
        .SetProperty(b => b.Name, b => NormalizeName(b.Name)));

This fails unless the method can be translated by the provider.

Keep SetProperty() expressions simple and SQL-translatable.

ExecuteUpdate Release History

  • EF Core 10:
    • JSON support: Added ExecuteUpdate support for relational JSON columns, allowing efficient bulk updates of JSON properties when mapped as complex types.
    • Easier dynamic updates: ExecuteUpdateAsync now accepts a regular lambda (not only expression trees), making dynamic and conditional updates much easier to write.
  • EF Core 9: Improved ExecuteUpdate to support complex type properties.
  • EF Core 8: Improved ExecuteUpdate and ExecuteDelete to support more complex queries (owned types, unions, and TPT), as long as all updates target a single database table.
  • EF Core 7: Introduced ExecuteUpdate and ExecuteDelete.
  • EF Core 2+: For older versions of EF Core or if you prefer the syntax provided by EF Extensions over using SetProperty, you can use UpdateFromQuery.

ExecuteUpdateAsync() was introduced in EF Core 7.

The main idea is the same across EF Core 7, 8, 9, and 10: update matching rows directly in the database without loading entities.

However, newer EF Core versions may improve translation support, provider behavior, and supported query shapes.

The exact behavior can still depend on the database provider.

External Resources - ExecuteUpdate

The following videos are useful if you want to see ExecuteUpdateAsync() in real EF Core examples, especially how it differs from tracked updates with SaveChangesAsync() and how it avoids unnecessary entity loading.

Video 1 - EF Core 7 - Performance Improvements With ExecuteUpdate and ExecuteDelete

Milan Jovanović demonstrates how to replace a slow foreach-based update with ExecuteUpdate. He applies a computed value, explains how the update runs directly in the database without tracking entities, and compares performance against the traditional approach.

Key timestamps:

  • 1:45 — Creating the new endpoint based on ExecuteUpdate
  • 3:00 — Applying a percentage salary increase with SetProperty
  • 4:25 — Why SaveChangesAsync() is not needed after ExecuteUpdateAsync()
  • 4:45 — SQL UPDATE generated by EF Core 7
  • 6:00 — Performance comparison: foreach update vs. ExecuteUpdate

Video 2 - Entity Framework 7 Makes Performing Updates and Deletes Easy

Israel Quiroz explains how ExecuteUpdateAsync() replaces the traditional workflow of retrieving an entity, modifying properties, and calling SaveChangesAsync(). He shows how a single database call can update several fields using SetProperty, without tracking entities.

Key timestamps:

  • 3:28 — Traditional update workflow: retrieve, assign, SaveChanges
  • 3:40 — ExecuteUpdateAsync with SetProperty
  • 4:00 — Chained SetProperty calls
  • 4:20 — Fewer database round-trips
  • 4:40 — No change tracking during the update

Video 3 - Spanish: Borrado y Actualizaciones Masivas - Nuevo de EF Core 7

Felipe Gavilan demonstrates mass updates using ExecuteUpdate with multiple SetProperty calls. He updates a timestamp and a string field, verifies the results in SQL Server, and shows how all changes are performed in one consolidated SQL UPDATE.

Key timestamps:

  • 6:20 — Starting the ExecuteUpdate example after reviewing existing rows
  • 8:12 — First SetProperty: assigning the update timestamp
  • 8:35 — Second SetProperty: appending text and reviewing the generated SQL
  • 9:08 — Verifying updated rows in SQL Server
  • 9:31 — Final numbers: 100k updates

Summary

ExecuteUpdateAsync() lets you update rows directly in the database without loading entities, tracking them, or calling SaveChangesAsync().

Key points:

  • it executes immediately
  • it works from an EF Core query
  • it uses SetProperty() to define updated columns
  • it can update one or multiple properties
  • it can use filters to control which rows are updated
  • it does not update tracked entity instances already loaded in memory
  • it is best for set-based updates that can be translated to SQL

Use SaveChangesAsync() when you need the normal tracked entity workflow.

Use BulkUpdate from Entity Framework Extensions when each row has different values coming from entities in memory or when you need advanced bulk options.

🚀 Ready to Supercharge EF Core?

Discover faster inserts, updates, deletes, and merges with Entity Framework Extensions. Try it in your own project and see the difference.

Get Started with EF Extensions →

If you want to compare ExecuteUpdateAsync() with the most closely related EF Core saving patterns, these pages are the best next step:

FAQ

Does ExecuteUpdateAsync require SaveChangesAsync?

No. ExecuteUpdateAsync() executes immediately in the database. You do not need to call SaveChangesAsync() afterward.

Does ExecuteUpdateAsync use the ChangeTracker?

No. ExecuteUpdateAsync() does not use the ChangeTracker and does not update tracked entity instances already loaded in memory.

Can ExecuteUpdateAsync update multiple columns?

Yes. You can chain multiple SetProperty() calls to update several columns in the same database command.

Can ExecuteUpdateAsync use the current column value?

Yes. The new value can be based on the current database value, as long as the expression can be translated to SQL.

When should I use ExecuteUpdateAsync instead of SaveChangesAsync?

Use ExecuteUpdateAsync() when you want to update rows directly in the database using a query. Use SaveChangesAsync() when you are working with tracked entities and need the normal EF Core change-tracking workflow.

Is ExecuteUpdateAsync the same as BulkUpdate?

No. ExecuteUpdateAsync() is a set-based update API. A bulk update library is usually better when each row has different values coming from a list of entities in memory.


Date Modified: 2026-07-26
Author:

Edit this page in GitHub

Got any EF Core Question?