Support EF Core ExecuteUpdate via IRewritableQueryable#17
Support EF Core ExecuteUpdate via IRewritableQueryable#17
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ExecuteUpdate/ExecuteUpdateAsync stubs to RelationalExtensions with [PolyfillTarget] routing (RelationalQueryableExtensions on EF Core 8, EntityFrameworkQueryableExtensions on EF Core 9). Enables modern C# syntax (switch expressions, null-conditional) inside SetProperty value lambdas. Fix generic method overload disambiguation in ReflectionFieldCache by checking parameter type patterns (IsGenericType vs IsGenericParameter) to correctly resolve SetProperty<P>(Func<T,P>, P) vs SetProperty<P>(Func<T,P>, Func<T,P>). Stubs are conditional on !NET10_0_OR_GREATER since EF Core 10 replaced SetPropertyCalls<T> with Action<UpdateSettersBuilder<T>>. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds ExecuteUpdate/ExecuteUpdateAsync support to ExpressiveSharp’s EF Core relational extensions by introducing intercepted stubs on IRewritableQueryable<T> (EF Core 8/9 only), plus a generator fix to correctly resolve ambiguous generic overloads when emitting reflection-based MethodInfo lookups.
Changes:
- Add EF Core 8/9
ExecuteUpdate/ExecuteUpdateAsyncpolyfill stubs forIRewritableQueryable<T>in the RelationalExtensions package (excluded on net10.0+). - Update generator method resolution logic (
ReflectionFieldCache) to better disambiguate generic overloads based on parameter-type patterns. - Add generator snapshot tests and EF Core SQLite integration tests covering
ExecuteUpdatewith constant, null-coalescing, switch expression, multiple setters, and async variants.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.GroupJoin_AnonymousResultSelector_GeneratesGenericInterceptor.verified.txt | Updates snapshot due to new overload-disambiguation logic in generated reflection lookups. |
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdateAsync_GeneratesInterceptor.verified.txt | New snapshot validating generated interceptor for ExecuteUpdateAsync. |
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithSwitchExpression.verified.txt | New snapshot validating switch-expression rewriting inside SetProperty value lambda. |
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_WithNullConditional.verified.txt | New snapshot validating null-conditional/coalesce rewriting inside SetProperty value lambda. |
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.ExecuteUpdate_SetProperty_ConstantValue.verified.txt | New snapshot validating constant-value setter overload selection and emission. |
| tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ExecuteUpdateTests.cs | New generator tests that compile mock EF Core APIs and verify produced interceptors/snapshots. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/PropertyTests.ExpressivePropertyToNavigationalProperty.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/MethodTests.TypesInBodyGetsFullyQualified.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/ConstructorTests.ProjectableConstructor_WithFullObject.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/CollectionExpressionTests.CollectionExpression_SpreadOnly.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/CollectionExpressionTests.CollectionExpression_MultipleSpreads.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/CollectionExpressionTests.CollectionExpression_ListWithSpread.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/CollectionExpressionTests.CollectionExpression_ArrayWithSpread.verified.txt | Snapshot updates from the refined reflection method selection predicate. |
| tests/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Tests/Models/ExecuteUpdateTestDbContext.cs | Adds EF Core test model + DbContext used for ExecuteUpdate integration tests. |
| tests/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Tests/ExecuteUpdateIntegrationTests.cs | Adds end-to-end SQLite tests for ExecuteUpdate/ExecuteUpdateAsync with modern C# syntax in setter lambdas. |
| src/ExpressiveSharp.Generator/Emitter/ReflectionFieldCache.cs | Adds parameter-type-pattern checks to disambiguate generic method overloads during reflection lookup emission. |
| src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Extensions/RewritableQueryableRelationalExtensions.cs | Adds intercepted EF Core 8/9 ExecuteUpdate stubs on IRewritableQueryable<T> with version-dependent PolyfillTarget routing. |
| .gitignore | Ignores .worktrees/ and normalizes docs dist ignore entry formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| paramChecks += $" && !m.GetParameters()[{i}].ParameterType.IsGenericType"; | ||
| else if (paramType is INamedTypeSymbol { IsGenericType: true }) | ||
| paramChecks += $" && m.GetParameters()[{i}].ParameterType.IsGenericType"; |
There was a problem hiding this comment.
The overload-disambiguation predicate treats type-parameter parameters as !ParameterType.IsGenericType. That also matches any non-generic concrete type (e.g., int, string), so it can still select the wrong overload when a competing overload has a non-generic parameter at the same position. For ITypeParameterSymbol parameters, the reflection check should be ParameterType.IsGenericParameter (and potentially also exclude IsGenericType) to precisely match a generic method's type parameter.
| paramChecks += $" && !m.GetParameters()[{i}].ParameterType.IsGenericType"; | |
| else if (paramType is INamedTypeSymbol { IsGenericType: true }) | |
| paramChecks += $" && m.GetParameters()[{i}].ParameterType.IsGenericType"; | |
| paramChecks += $" && m.GetParameters()[{i}].ParameterType.IsGenericParameter && !m.GetParameters()[{i}].ParameterType.IsGenericType"; | |
| else if (paramType is INamedTypeSymbol { IsGenericType: true }) | |
| paramChecks += $" && m.GetParameters()[{i}].ParameterType.IsGenericType && !m.GetParameters()[{i}].ParameterType.IsGenericParameter"; |
Address CodeQL string-concatenation-in-loop suggestion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ExecuteUpdate/ExecuteUpdateAsync section to rewritable-queryable.md and ef-core-integration.md with usage examples, version compatibility notes, and updated RelationalExtensions package description. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Closes #6
ExecuteUpdate/ExecuteUpdateAsyncstubs onIRewritableQueryable<T>in the RelationalExtensions project with[PolyfillTarget]routing (EF Core 8 →RelationalQueryableExtensions, EF Core 9 →EntityFrameworkQueryableExtensions)ReflectionFieldCacheto correctly resolveSetProperty<P>(Func<T,P>, P)vsSetProperty<P>(Func<T,P>, Func<T,P>)by checking parameter type patternsSetPropertyCalls<T>withAction<UpdateSettersBuilder<T>>What this enables
Modern C# syntax inside
SetPropertyvalue expressions that the C# compiler normally rejects in expression trees:Test plan
🤖 Generated with Claude Code