What is ExecuteUpdate
ExecuteUpdate is an EF Core feature introduced in EF Core 7 (see also ExecuteDelete) that lets you update database rows directly in SQL without loading entities into memory, without using the Change Tracker, and without calling SaveChanges.
Instead of modifying entities and calling SaveChanges, you define your update logic through LINQ. You use the SetProperty method to choose which property to update and what value to set. EF Core then translates everything into a single UPDATE statement executed on the database server.
ExecuteUpdate is available in both synchronous and asynchronous versions: ExecuteUpdate and ExecuteUpdateAsync.
Basic example:
await context.Customers
.Where(c => c.Age > 30)
.ExecuteUpdateAsync(s =>
s.SetProperty(c => c.Age, c => c.Age + 1));
Generated SQL:
UPDATE [c]
SET [c].[Age] = [c].[Age] + 1
FROM [Customers] AS [c]
WHERE [c].[Age] > 30
In short, it tells the database: "update all matching rows like this."
This makes updates dramatically faster and is ideal for bulk or set-based operations.
ExecuteUpdate Requirements
- EF Core Version: EF Core 7.0+
- Supported Providers: SQL Server, SQLite, PostgreSQL, MySQL, Oracle
- Unsupported Providers: MariaDB, InMemory
- Additional Requirements:
- Update logic must be SQL-translatable
- Expressions in
SetPropertymust not rely on custom C# functions - You must specify properties explicitly (EF does not detect changes)
For SetProperty:
Supported:
- Constants
- Column references
- Basic math and string operations
Not supported:
- C# methods
- Business logic
- Random values
- Values coming from in-memory collections
TL;DR – ExecuteUpdate
- Runs set-based updates directly in SQL.
- Does not require calling
SaveChanges. - Does not load entities into memory (massive performance boost).
- Does not use the Change Tracker.
- Does not update the Change Tracker.
- Update rules must be SQL-translatable.
- Best for batch updates and simple value transformations.
- EF Extensions supports real bulk updates when per-row values are needed.
ExecuteUpdate Examples
Update with concatenation
The following example updates the Name property of every author:
using (var context = new LibraryContext())
{
await context.Authors.ExecuteUpdateAsync(
s => s.SetProperty(b => b.Name, b => b.Name + " *Updated!*"));
}
The first parameter of SetProperty specifies which property to update (Author.Name).
The second parameter specifies how the new value is calculated, by using the existing value and appending " *Updated!*".
Generated SQL:
UPDATE [a]
SET [a].[Name] = [a].[Name] + N' *Updated!*'
FROM [Authors] AS [a]
Update with concatenation and filters
You can call SetProperty multiple times to update more than one property.
The following example updates the Title and Content of all books published before 2018:
using (var context = new LibraryContext())
{
context.Books
.Where(b => b.PublishedOn.Year < 2018)
.ExecuteUpdateAsync(s => s
.SetProperty(b => b.Title, b => b.Title + " (" + b.PublishedOn.Year + ")")
.SetProperty(b => b.Content, b => b.Content + " (This content was published in " + b.PublishedOn.Year + ")"));
}
In this case, the generated SQL is more complex:
UPDATE [b]
SET [b].[Content] = (([b].[Content] + N' (This content was published in ') + COALESCE(CAST(DATEPART(year, [b].[PublishedOn]) AS nvarchar(max)), N'')) + N')',
[b].[Title] = (([b].[Title] + N' (') + COALESCE(CAST(DATEPART(year, [b].[PublishedOn]) AS nvarchar(max)), N'')) + N')'
FROM [Books] AS [b]
WHERE DATEPART(year, [b].[PublishedOn]) < 2018
Update using navigation filters
ExecuteUpdate also allows you to use filters that reference other tables.
The following example updates all tags from old posts:
using (var context = new BloggingContext())
{
context.Tags
.Where(t => t.Posts.All(p => p.PublishedOn.Year < 2022))
.ExecuteUpdateAsync(s => s.SetProperty(t => t.Text, t => t.Text + " (old)"));
}
Generated SQL:
UPDATE [t]
SET [t].[Text] = [t].[Text] + N' (old)'
FROM [Tags] AS [t]
WHERE NOT EXISTS (
SELECT 1
FROM [PostTag] AS [p]
INNER JOIN [Posts] AS [p0] ON [p].[PostsId] = [p0].[Id]
WHERE [t].[Id] = [p].[TagsId] AND NOT (DATEPART(year, [p0].[PublishedOn]) < 2022))
ExecuteUpdate – EF Core 10 Enhancement
ExecuteUpdate now accepts a regular lambda, not only expression trees.
This allows conditional logic (if, else if, else) as long as all SetProperty calls remain SQL-translatable.
bool nameChanged = true; // Local variable, not a column
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged)
{
s.SetProperty(b => b.Name, "foo");
}
});
ExecuteUpdate Release History
- EF Core 10.0:
- JSON support: Added
ExecuteUpdatesupport for relational JSON columns, allowing efficient bulk updates of JSON properties when mapped as complex types. - Easier dynamic updates:
ExecuteUpdateAsyncnow accepts a regular lambda (not only expression trees), making dynamic and conditional updates much easier to write.
- JSON support: Added
- EF Core 9.0: Improved
ExecuteUpdateto support complex type properties, allowing you to set the entire complex type at once while still updating each mapped column explicitly. - EF Core 8.0: Improved
ExecuteUpdateandExecuteDeleteto support more complex queries (owned types, unions, and TPT), as long as all updates target a single database table. - EF Core 7.0: Introduced
ExecuteUpdateandExecuteDelete. - EF Core 2.0+: For older versions of EF Core or if you prefer the syntax provided by EF Extensions over using
SetProperty, you can use UpdateFromQuery.
Is ExecuteUpdate a real “Bulk Update”?
Short answer: not quite.
It does update rows in bulk in a database. However, it doesn’t let you bulk update entities with their own values, like the BulkUpdate method from Entity Framework Extensions does. The difference is simple:
ExecuteUpdate: Tell the database, “update all matching rows like this.”- BulkUpdate: Take this list of entities, each with its own values, send them efficiently to the server, and apply each value to its matching row.
// ExecuteUpdate
await context.Customers
.Where(c => c.Age > 30)
.ExecuteUpdateAsync(s =>
s.SetProperty(c => c.Age, c => c.Age + 1));
// BulkUpdate (with EF Extensions)
await context.BulkUpdateAsync(customers);
What’s the practical difference?
| Question | ExecuteUpdate (EF Core) |
BulkUpdate (EF Extensions) | |
|---|---|---|---|
| How are values applied? | One rule for all rows | Values come from each entity | |
| Where does the data come from? | Computed in SQL | In-memory entity list | |
| How many rows? | Good for thousands to hundreds of thousands | Designed for millions | |
| Different values per row? | ❌ No | ✅ Yes | |
| Related entities? | ❌ No | ✅ Yes (IncludeGraph) | |
| Change Tracker sync? | ❌ No | ✅ Optional (can sync output values) |
A quick mental model
ExecuteUpdate= “Tell the database: update all matching rows like this.” Great for uniform changes (for example, “mark all expired coupons as inactive”).- Real bulk update = “Take this list of entities with their own values, ship them efficiently to the server, and apply each value to its matching row.” Essential when each row is different or volumes are huge.
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 |
Important: For
BulkUpdateandSaveChanges, around 250 ms of this time is spent materializing the list of entities from the database before the update starts. This step is required becauseBulkUpdateandSaveChangesworks from entities, not from a LINQ rule.
Bulk Update – Concrete example
Goal: update 1,000,000 products with different NewPrice values coming from memory.
// @nuget: Z.EntityFramework.Extensions.EFCore
using Z.EntityFramework.Extensions;
// Per-row values (each product has its own NewPrice)
var products = GetMillionProductsWithNewPrices();
// Real bulk update: pushes values, uses staging + UPDATE JOIN under the hood
context.BulkUpdate(products, options =>
{
options.ColumnInputExpression = p => new { p.Id, p.NewPrice }; // update only needed columns
options.BatchSize = 10000;
});
Doing the same with ExecuteUpdate is not feasible without first staging your values yourself (for example, creating a temp table, loading it, and then writing an UPDATE JOIN).
EF Extensions automates that entire pipeline for you.
When to use which?
| Scenario | Recommended |
|---|---|
| Same update rule for all rows | ExecuteUpdate |
| Complex update rules | ExecuteUpdate (if SQL-translatable) |
| Per-row different values | EF Extensions BulkUpdate |
| Large-scale data updates | ExecuteUpdate or EF Extensions BulkUpdate |
| Need events, validation, or Change Tracker updates | SaveChanges |
Use
ExecuteUpdatewhen:- The change is uniform for all matched rows
- You want a zero-dependency solution inside EF Core
- The dataset is modest to medium and you don’t need advanced options
Use a real bulk update (Entity Framework Extensions) when:
- Each row needs a different value from your in-memory data
- You’re touching hundreds of thousands to millions of rows
- You need upsert/merge, graph updates, or advanced options
Bottom line:
ExecuteUpdateis a powerful set-based tool. A real bulk update is a high-throughput data pipeline built for scale, per-row values, and advanced orchestration. They complement each other rather than compete.
Additional Resources – ExecuteUpdate
📘 Recommended Reading
- Microsoft - ExecuteUpdate
- Entity Framework Tutorials - ExecuteUpdate
- ExecuteUpdateAsync Update to Allow Non-Expression Lambda
🎥 Recommended Videos
EF Core 7 – Performance Improvements With the New ExecuteUpdate & ExecuteDelete, por Milan Jovanović
Milan demonstrates how to replace a slow foreach-based update with the new ExecuteUpdate API in EF Core 7. He applies a computed value (Salary * 1.1), explains how the update runs directly in the database without tracking entities, and compares performance against the traditional approach.
Key timestamps:
- 01:45 – Creating the new endpoint based on ExecuteUpdate
- 02:30 – Filtering employees by CompanyId using LINQ
- 03:00 – Applying a percentage salary increase with SetProperty
- 03:45 – No tracking + no SaveChanges call
- 04:45 – SQL UPDATE generated by EF Core 7
- 06:00 – Performance comparison: foreach update vs ExecuteUpdate
Entity Framework 7 Makes Performing UPDATES and DELETES Easy!, por Israel Quiroz
Israel explains how ExecuteUpdateAsync replaces the traditional workflow of retrieving an entity, modifying properties, and calling SaveChanges. He shows how a single database call can update several fields using SetProperty, without tracking entities.
Key timestamps:
- 03:28 – Traditional update workflow (retrieve → assign → SaveChanges)
- 03:40 – ExecuteUpdateAsync with SetProperty
- 04:00 – Chained SetProperty calls (multiple fields)
- 04:20 – Fewer database round-trips
- 04:40 – No change tracking during the update
(SPANISH) Borrado y Actualizaciones Masivas – Nuevo de EF Core 7, por Felipe Gavilan
Felipe 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:
- 05:32 – First SetProperty: assigning the update timestamp
- 06:48 – Second SetProperty: appending text to Nombre
- 07:48 – Running the mass update and reviewing results
- 08:31 – Beginning of performance comparison
- 09:31 – Final numbers: 100k updates (8s → 0.2s)
Summary & Next Steps – ExecuteUpdate
ExecuteUpdate is a powerful EF Core feature that enables fast, set-based updates without Change Tracker overhead. It is ideal for bulk data maintenance and simple transformations.
Use ExecuteUpdate when:
- You want a simple update rule applied to many records
- You want performance without loading entities into memory
Use EF Extensions BulkUpdate when:
- Each row requires a unique value
- You need advanced batching,
MERGE, or auditing - Your database provider does not support
ExecuteUpdate
Next steps:
- Explore the EF Extensions BulkUpdate documentation
- Learn about ExecuteDelete