Regular Lambdas in EF Core 10: A Small Feature That Makes a Big Difference
Why EF Core 10’s support for statement lambdas in
ExecuteUpdate()improves readability, maintainability, and enterprise application development.
As developers, we often get excited about major features — performance improvements, new LINQ capabilities, support for the latest .NET release. But some of the most impactful changes are the small quality-of-life enhancements that eliminate everyday friction.
One such enhancement in Entity Framework Core 10 is the ability to use regular (statement) lambdas inside ExecuteUpdate().
At first glance, this might look like a minor syntax change. In reality, it significantly improves how we write dynamic bulk update logic — especially in enterprise applications where business rules change frequently and conditional updates are the norm, not the exception.
If you’ve been building with ASP.NET Core and EF Core for a while, this is one of those features you’ll start using on day one and wonder how you lived without it.
Let’s walk through why.
The Situation Before EF Core 10
Introduced in EF Core 7, ExecuteUpdate() allowed developers to perform bulk updates directly in the database without loading entities into memory.
For example:
await context.Products
.Where(p => p.Stock < 10)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.IsActive, false)
.SetProperty(p => p.Note, "Low Stock"));This generates a single SQL UPDATE statement instead of:
- Loading thousands of records into memory
- Modifying each entity in a loop
- Calling
SaveChanges()
That was already a huge performance win.
However, there was one important limitation.
The Limitation: Expression Lambdas Only
The update configuration only accepted an expression lambda:
s => s.SetProperty(...)
.SetProperty(...)Notice the shape: a single expression, chained together.
Because it’s an expression, C# does not allow:
if/elseswitch- Loops
- Local variable declarations
- Multiple statements
For simple, static updates, this wasn’t a problem.
For real-world enterprise applications, it quickly became one.
A Real Business Scenario
Imagine an inventory system. When stock falls below a threshold, the system should:
- Mark the product as inactive
- Optionally add a note
- Optionally notify purchasing
- Optionally update the last modified date
Whether each property gets updated depends on configuration — feature flags, admin settings, or runtime business rules.
Before EF Core 10, developers usually ended up writing something like this:
bool addNote = false;
if (addNote)
{
await context.Products
.Where(p => p.Stock < 10)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.IsActive, false)
.SetProperty(p => p.Note, "Low Stock"));
}
else
{
await context.Products
.Where(p => p.Stock < 10)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.IsActive, false));
}Notice the problem?
The query is duplicated.
The Where clause is repeated. The base SetProperty call is repeated. Now scale that to five optional fields — or ten — and the code becomes difficult to read, review, and maintain.
Every time a business rule changes, you’re updating multiple branches instead of one cohesive block.
EF Core 10 Changes Everything
EF Core 10 now supports statement lambdas in ExecuteUpdate().
Instead of returning a single expression, you can write normal C# code:
bool addNote = true;
await context.Products
.Where(p => p.Stock < 10)
.ExecuteUpdateAsync(s =>
{
s.SetProperty(p => p.IsActive, false);
if (addNote)
{
s.SetProperty(p => p.Note, "Low Stock");
}
});This looks like everyday C#.
And that’s exactly why it’s powerful.
Expression Lambda vs. Statement Lambda
If you’re coming from a LINQ background, the distinction is worth making explicit:
Expression LambdaStatement Lambda
x => x + 1
x => { return x + 1; }
Single expression
Multiple statements
No if
✅ if supported
No loops
✅ Loops supported
No local variables
✅ Local variables supported
Harder for dynamic updates
Excellent for dynamic updates
Before EF Core 10, ExecuteUpdate() lived entirely in the left column. Now it supports both.
Why This Matters
This feature isn’t really about writing less code.
Get Dushyantha Kalehewatte’s stories in your inbox
Join Medium for free to get updates from this writer.
It’s about writing maintainable code.
Instead of branching outside the query, you configure the update in one place. The benefits stack up quickly:
- Cleaner business logic — rules live where the update happens
- Less duplicated code — one query, one
Where, one execution path - Easier maintenance — change a rule in one block, not three
- Better readability — reviewers can follow the intent without jumping between branches
- Simpler feature toggles — boolean flags map directly to conditional
SetPropertycalls
Dynamic Updates Based on Admin Configuration
Suppose an admin panel lets users choose which fields to update:
bool updatePrice = true;
bool updateName = false;
bool updateStock = true;
await context.Products.ExecuteUpdateAsync(s =>
{
if (updatePrice)
s.SetProperty(p => p.Price, 100);
if (updateName)
s.SetProperty(p => p.Name, "New Product");
if (updateStock)
s.SetProperty(p => p.Stock, 50);
});One query. One execution. Clear intent.
Before EF Core 10, this would have required combinatorial if/else blocks — or separate ExecuteUpdateAsync calls for every combination of flags.
Another Example: HR and Payroll
Consider updating employees in bulk:
bool giveBonus = true;
bool promote = false;
await context.Employees
.Where(e => e.Department == "IT")
.ExecuteUpdateAsync(s =>
{
if (giveBonus)
s.SetProperty(e => e.Bonus, 1000);
if (promote)
s.SetProperty(e => e.Level, "Senior");
});Very readable. Very maintainable. And it still generates efficient SQL.
Dynamic API Updates Become Much Cleaner
This pattern shines in REST APIs that accept partial updates.
public class UpdateProductRequest
{
public string? Name { get; set; }
public decimal? Price { get; set; }
public int? Stock { get; set; }
}Previously, partial updates often required building complicated expressions, helper methods, or multiple update paths.
With EF Core 10:
await context.Products
.Where(p => p.Id == id)
.ExecuteUpdateAsync(s =>
{
if (request.Name != null)
s.SetProperty(p => p.Name, request.Name);
if (request.Price.HasValue)
s.SetProperty(p => p.Price, request.Price.Value);
if (request.Stock.HasValue)
s.SetProperty(p => p.Stock, request.Stock.Value);
});This closely mirrors the incoming request. A new developer can read it and understand exactly what’s happening — no expression tree gymnastics required.
Enterprise Example: Insurance Systems
Having spent years building enterprise insurance applications, I often encounter scenarios where updates depend on configurable business rules.
Take policy expiry processing. Business requirements might say:
- Update policy status to “Expired”
- Mark the policy inactive (if configured)
- Record audit information (if audit is enabled)
- Queue a notification (if notifications are on)
Before EF Core 10, each combination of flags meant another branch with a duplicated query.
With EF Core 10:
await context.Policies
.Where(p => p.ExpiryDate < DateTime.Today)
.ExecuteUpdateAsync(s =>
{
s.SetProperty(p => p.Status, "Expired");
if (markInactive)
s.SetProperty(p => p.IsActive, false);
if (enableAudit)
s.SetProperty(p => p.LastUpdatedBy, currentUser);
if (queueNotification)
s.SetProperty(p => p.NotificationPending, true);
});Instead of scattering conditional logic across multiple update statements, everything stays in one cohesive block.
For large enterprise systems — insurance, ERP, HR — that translates into code that’s easier to evolve as requirements change.
Does This Affect SQL Performance?
No. And this is worth stating clearly because it’s a common misconception.
Adding if statements inside the lambda does not generate more complex SQL. The conditions are evaluated before EF Core builds the SQL statement.
If addNote = true:
UPDATE Products
SET
IsActive = 0,
Note = 'Low Stock'
WHERE Stock < 10;If addNote = false:
UPDATE Products
SET
IsActive = 0
WHERE Stock < 10;Only the SetProperty() calls that actually execute are translated into SQL. You get the readability of conditional C# with the efficiency of a single bulk UPDATE — no entities loaded, no round trips per row.
Before vs. After at a Glance
Before EF Core 10EF Core 10
Expression lambda only
Statement lambda supported
No if/else
✅ if/else supported
No local variables
✅ Local variables supported
Limited dynamic configuration
✅ Easy conditional updates
More duplicated code
✅ Cleaner and more maintainable
Why Tech Leads Should Care
As applications grow, readability becomes just as important as performance.
Small language improvements like this:
- Reduce duplicated logic across the codebase
- Simplify code reviews — one block to inspect instead of many
- Lower long-term maintenance costs
- Make onboarding easier for new developers
- Help teams express business rules more naturally in code
These improvements may not make the release headlines, but they make a noticeable difference in day-to-day development — especially on teams where bulk updates and configurable business rules are part of the daily workflow.
From a tech lead perspective, features like this are the kind you want your team to adopt early. The migration cost is zero — existing expression-lambda code continues to work. The benefit shows up the next time someone writes a conditional bulk update.






