Skip to content

[release/10.0] Fix SetProperty discard lambda failing for nullable value type properties in ExecuteUpdate#38007

Open
roji wants to merge 1 commit intodotnet:release/10.0from
roji:backport/37974-to-10.0
Open

[release/10.0] Fix SetProperty discard lambda failing for nullable value type properties in ExecuteUpdate#38007
roji wants to merge 1 commit intodotnet:release/10.0from
roji:backport/37974-to-10.0

Conversation

@roji
Copy link
Copy Markdown
Member

@roji roji commented Mar 26, 2026

Fixes #37974
Backports #37975

Description
When using ExecuteUpdate with SetProperty to assign a constant or literal value to a nullable value type property (e.g. int?) via a discard lambda (_ => 1), EF Core throws InvalidOperationException: No coercion operator is defined between types Func<Entity, int?> and int. This was introduced by the EF Core 10 refactoring of ExecuteUpdate from Expression<Func<...>> to Func<...> for the setter parameter.

The root cause is in ExpressionTreeFuncletizer.ProcessEvaluatableRoot: when constantizing a value that needs a nullable conversion (e.g. intint?), ConvertIfNeeded internally calls Visit, which corrupts the _state field back to EvaluatableWithoutCapturedVariable. This causes the parent VisitLambda to incorrectly treat the entire lambda as evaluatable, compiling it into a Func<Entity, int?> delegate. The downstream TranslateSetterValueSelector then receives a QueryParameterExpression instead of a LambdaExpression and throws.

Customer impact
Users calling ExecuteUpdateAsync with a constant/literal value assigned to a nullable value type property via a discard lambda get an InvalidOperationException at query compilation time. This is a common pattern:

const int SystemUserId = 1;
await ctx.Orders.ExecuteUpdateAsync(s =>
    s.SetProperty(p => p.UpdatedById, _ => SystemUserId)); // UpdatedById is int?, throws

Workarounds exist: cast the value to int? explicitly, or use a named parameter (p =>) instead of a discard (_ =>). Both workarounds are somewhat discoverable from the error message but unintuitive.

How found
User reported on EF Core 10.0.0. One additional user has also reported being affected. The issue has 2 reactions (+1 and eyes).

Regression
Yes, this is a regression from EF Core 9 to EF Core 10. It was introduced as a side effect of the ExecuteUpdate refactoring that changed the setter parameter from Expression<Func<...>> to Func<...> (the breaking change documented at https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/breaking-changes#execute-update-expression).

Testing
Added 1 new test (Update_Where_set_nullable_int_constant_via_discard_lambda) with SQL baseline assertions in both SQLite and SQL Server test classes.

Risk
Extremely low. The fix is a single-line state reset (state = State.NoEvaluatability) after ConvertIfNeeded returns, restoring the state that was already set earlier in the method but got corrupted by the internal Visit call. Quirk added (Microsoft.EntityFrameworkCore.Issue37974) to opt out.

…ties in ExecuteUpdate

Backport of dotnet#37975 to release/10.0.

In ExpressionTreeFuncletizer.ProcessEvaluatableRoot, ConvertIfNeeded calls Visit
which corrupts _state back to EvaluatableWithoutCapturedVariable when wrapping a
constant int to int?. This causes the parent VisitLambda to treat the lambda as
evaluatable, compiling it to a Func and breaking TranslateSetterValueSelector.

Fix: reset state to NoEvaluatability after ConvertIfNeeded returns.
Added quirk Microsoft.EntityFrameworkCore.Issue37974 to opt out.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji requested a review from a team as a code owner March 26, 2026 11:37
Copilot AI review requested due to automatic review settings March 26, 2026 11:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an EF Core 10 regression where ExecuteUpdate/SetProperty using a discard lambda (_ => 1) fails when assigning a constant/literal to a nullable value type property (e.g. int?), by preventing ExpressionTreeFuncletizer state corruption during constantization and adding coverage for the scenario.

Changes:

  • Reset funcletizer state after ConvertIfNeeded when constantizing an evaluatable root (with an AppContext quirk to opt out).
  • Add a new bulk update test covering SetProperty(..., _ => 1) for a nullable int property in the Northwind spec tests.
  • Add SQLite and SQL Server SQL baseline assertions for the new test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs Resets state after ConvertIfNeeded to avoid _state corruption when constantizing, gated by an AppContext switch.
test/EFCore.Specification.Tests/BulkUpdates/NorthwindBulkUpdatesTestBase.cs Adds provider-agnostic test for updating Product.SupplierID (int?) via discard-lambda constant setter.
test/EFCore.Sqlite.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqliteTest.cs Adds SQLite SQL baseline for the new nullable-int discard-lambda update test.
test/EFCore.SqlServer.FunctionalTests/BulkUpdates/NorthwindBulkUpdatesSqlServerTest.cs Adds SQL Server SQL baseline for the new nullable-int discard-lambda update test.

@AndriySvyryd AndriySvyryd removed their assignment Mar 26, 2026
Copy link
Copy Markdown
Member

@artl93 artl93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Regression. Customer reported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants