Replace CTE approach with CROSS APPLY / CROSS JOIN LATERAL for reused block-body locals#5
Draft
Replace CTE approach with CROSS APPLY / CROSS JOIN LATERAL for reused block-body locals#5
Conversation
… CteAwareQuerySqlGenerator, registration, and BlockStatementConverter reference tracking Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
…d scalar subquery safety, add generator test Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Add CTE support for local variable deduplication in EF Core SQL translation
Add CTE infrastructure for SQL deduplication of repeated local variables in block-bodied projectable methods
Mar 15, 2026
Copilot stopped work on behalf of
PhenX due to an error
March 15, 2026 08:53
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
…framework support Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
Add CTE infrastructure for SQL deduplication of repeated local variables in block-bodied projectable methods
Add CTE deduplication tests, NormalizedSelectExpressionComparer, and net9.0 target support
Mar 15, 2026
… it in expression replacer Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
Add CTE deduplication tests, NormalizedSelectExpressionComparer, and net9.0 target support
Emit Mar 15, 2026
Variable.Wrap for reused block-body locals; add net9.0 target
…PLY, postprocessor Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
Emit
Add CTE generation: deduplicate subqueries via WITH clause, reused block-body locals via CROSS APPLY
Mar 15, 2026
Variable.Wrap for reused block-body locals; add net9.0 target…rking for reused locals Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
Add CTE generation: deduplicate subqueries via WITH clause, reused block-body locals via CROSS APPLY
CTE generation for block-body reused locals via CROSS APPLY (EF Core 10 / SQL Server)
Mar 15, 2026
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
CTE generation for block-body reused locals via CROSS APPLY (EF Core 10 / SQL Server)
Replace CTE with CROSS APPLY / CROSS JOIN LATERAL for reused block-body locals
Mar 15, 2026
…or factory Co-authored-by: PhenX <42170+PhenX@users.noreply.github.com>
Copilot
AI
changed the title
Replace CTE with CROSS APPLY / CROSS JOIN LATERAL for reused block-body locals
Replace CTE approach with CROSS APPLY / CROSS JOIN LATERAL for reused block-body locals
Mar 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Drops the CTE-based deduplication strategy in favour of SQL
CROSS APPLY(SQL Server) /CROSS JOIN LATERAL(PostgreSQL) inline subqueries for reused local variables in block-bodied[Projectable]methods. Extends support to EF Core 8 and 9, and uses the original C# variable name as the column alias inside the subquery.Renamed / removed
CteDeduplicatingRewriter,NormalizedSelectExpressionComparer,CteTableExpression,CteAwareQuerySqlGenerator,CteAwareQuerySqlGeneratorFactoryInlineSubqueryExpression+ProjectablesQuerySqlGenerator/ProjectablesQuerySqlGeneratorFactorySQL generation
CROSS APPLY (SELECT <expr> AS [varName]) AS [v]CROSS JOIN LATERAL (SELECT <expr> AS "varName") AS "v"v/v0/v1… (EF Core constraint on alias format)PostgreSQL detection
UsesLateralJoinnow checks theSqlGenerationHelperassembly name first (explicitly excludes SQL Server) with the double-quote delimiter as a secondary heuristic — more robust than delimiter-only detection.EF Core 8 / 9 compatibility
ProjectablesParameterBasedSqlProcessorFactory(active onnet8.0andnet9.0only) decoratesIRelationalParameterBasedSqlProcessorFactory. ItsOptimizeoverride temporarily removesCrossApplyExpression(InlineSubqueryExpression)entries from_tablesaround the provider's nullability pass (which throws on unknownTableExpressionBasesubtypes in EF 8/9), then restores them so the SQL generator can still emit theCROSS APPLYclauses.Tests
LocalVariableReuseTests— 12 scenarios: single/multi variable reuse, reuse inWHERE, chained projectable calls, conditional expressions (SQL Server)LocalVariableReuseLateralTests— mirrors key scenarios for PostgreSQLCROSS JOIN LATERALnet8.0,net9.0,net10.0Original prompt
Add CTE Support for Local Variable Deduplication in EF Core SQL Translation
Background
The project currently rewrites C# expression trees (including local variable declarations in blocks) into EF Core-compatible projections. The
BlockStatementConverterinsrc/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/BlockStatementConverter.cshandles local variables declared in projectable expressions — but when a local variable is referenced multiple times, its defining sub-expression gets copied/duplicated in the resulting SQL expression tree.This causes the same sub-query (or complex SQL fragment) to be emitted multiple times in the generated SQL, instead of being factored out into a SQL Common Table Expression (CTE) for reuse.
Goal
Introduce CTE-based deduplication for local variables that are referenced more than once in the translated SQL tree. When a local variable's defining expression would be inlined more than once, it should instead be:
WITH cteName AS (...)clauseWhat Needs to Be Done
1. Custom
CteTableExpression(new file)Create a new
TableExpressionBasesubclass representing a CTE definition. It should:TableExpressionBase(fromMicrosoft.EntityFrameworkCore.Query.SqlExpressions)SelectExpressionas the CTE body (Inner)Alias/CteNameClone,WithAlias,Quote(can throwNotSupportedExceptionfor now),Print,VisitChildrenSuggested location:
src/EntityFrameworkCore.Projectables/Query/SqlExpressions/CteTableExpression.cs2. Custom
CteAwareQuerySqlGenerator(new file)Override
QuerySqlGeneratorto handleCteTableExpressionnodes:GenerateRootCommandto emit theWITH cteName AS (...)preamble before the mainSELECTCteTableExpressions in tree-walk order (depth-first, so nested CTEs appear before their consumers)VisitExtensionto handleCteTableExpressionas a FROM clause reference (emitting just the name + alias, not the body again)Suggested location:
src/EntityFrameworkCore.Projectables/Query/CteAwareQuerySqlGenerator.cs3. Custom
CteAwareQuerySqlGeneratorFactory(new file)Implement
IQuerySqlGeneratorFactoryto instantiateCteAwareQuerySqlGenerator.Suggested location:
src/EntityFrameworkCore.Projectables/Query/CteAwareQuerySqlGeneratorFactory.cs4. Registration
Register the custom factory via
ReplaceService<IQuerySqlGeneratorFactory, CteAwareQuerySqlGeneratorFactory>()in the existing EF Core service registration extension (whereverProjectablesExtensionor similar options extension hooks intoIDbContextOptionsBuilder).5.
BlockStatementConverter— CTE candidate trackingIn
src/EntityFrameworkCore.Projectables.Generator/SyntaxRewriters/BlockStatementConverter.cs, update the local variable handling logic so that:var x = someExpression;), the defining expression is not immediately inlined everywhere.SelectExpressionin aCteTableExpressionand replace all reference sites withColumnExpressions pointing at the CTE.6.
CteDeduplicatingRewriter(optional utility, new file)Optionally add a reusable
ExpressionVisitorsubclass that:SelectExpressionsubtree by structural equality (usingExpressionEqualityComparer.Instance)CteTableExpressionwrappers +ColumnExpressionreferencesSuggested location:
src/EntityFrameworkCore.Projectables/Query/CteDeduplicatingRewriter.csKey Technical Notes
TableExpressionBaseis inMicrosoft.EntityFrameworkCore.Query.SqlExpressionsnamespaceQuerySqlGeneratoris inMicrosoft.EntityFrameworkCore.QuerynamespaceIQuerySqlGeneratorFactoryis inMicrosoft.EntityFrameworkCore.QuerynamespaceExpressionEqualityComparer.Instance(already in EF Core) handles structural equality of SQL expression treesWITHCTE syntax is supported by SQL Server, PostgreSQL, SQLite (3.8.3+), MySQL 8+, and MariaDB 10.2+QuerySqlGenerator.CheckComposableSqlalready acceptsWITHas a valid composable SQL startColumnExpressionreferences into the CTE must useTableAlias == cteTable.Alias(the outer alias, not the innerSelectExpression's alias)Quote()can throwNotSupportedExceptioninitially — precompiled query support can be added laterReference
The approach was designed in coll...
This pull request was created from Copilot chat.
💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.