Skip to content

Fix #8262 ToPageAsync() attempting to select invalid properties - #9840

Merged
michaelstaib merged 10 commits into
ChilliCream:mainfrom
Tommsy64:fix/8262-orderby-props-nested-selector
Jul 11, 2026
Merged

Fix #8262 ToPageAsync() attempting to select invalid properties#9840
michaelstaib merged 10 commits into
ChilliCream:mainfrom
Tommsy64:fix/8262-orderby-props-nested-selector

Conversation

@Tommsy64

@Tommsy64 Tommsy64 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #8262 and adds associated tests.

The root cause was the paging expression visitors walking the entire query expression tree and treating every OrderBy/ThenBy they encountered as a pagination concern, including order operations inside lambda bodies.

The first commit addresses this issue in the four location this occurs by overriding VisitLambda. The second commit moves the duplicated override into a shared base class.

@Tommsy64

Tommsy64 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

I added a third commit that fixes a similar case when a sort key itself contains a nested OrderBy:

Sorting: new SortDefinition<Brand>()
    .AddAscending(t => t.Products.OrderBy(p => p.Price).First().Price)
    .AddAscending(t => t.Id)

The visitor now tracks the key lambda's parameter and collects only members rooted directly at it. I'm sure queries could still be constructed that break the paging expression visitors, but this fix is reasonably scoped and covers the common cases.

@Tommsy64
Tommsy64 marked this pull request as ready for review June 3, 2026 22:33
Copilot AI review requested due to automatic review settings June 3, 2026 22:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds regression coverage and refactors pagination-related expression visitors to avoid traversing nested lambdas (e.g., OrderBy inside projections/predicates), preventing accidental hoisting/collection of non-pagination order keys during cursor generation.

Changes:

  • Added new paging regression tests plus snapshots for NET8/NET9/NET10 target frameworks.
  • Introduced QueryChainVisitor to restrict visitors to the top-level LINQ method chain (excluding lambda bodies by default).
  • Updated visitors/parsers used by paging (cursor parsing, order reversing, order property extraction, order removal) to derive from QueryChainVisitor.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperTests.cs Adds regression tests for nested OrderBy in selector/predicate/order keys and cursor creation.
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Forward_Paging_NET8_0.md Snapshot for SQL/expression when nested OrderBy exists in selector (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Forward_Paging_NET9_0.md Snapshot for SQL/expression when nested OrderBy exists in selector (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Forward_Paging_NET10_0.md Snapshot for SQL/expression when nested OrderBy exists in selector (NET10).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Does_Not_Hoist_Inner_Order_Properties_NET8_0.md Snapshot verifying inner order properties are not hoisted (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Does_Not_Hoist_Inner_Order_Properties_NET9_0.md Snapshot verifying inner order properties are not hoisted (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Selector_With_Nested_OrderBy_Does_Not_Hoist_Inner_Order_Properties_NET10_0.md Snapshot verifying inner order properties are not hoisted (NET10).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Forward_Paging_NET8_0.md Snapshot for nested OrderBy in predicate forward paging (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Forward_Paging_NET9_0.md Snapshot for nested OrderBy in predicate forward paging (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Forward_Paging_NET10_0.md Snapshot for nested OrderBy in predicate forward paging (NET10).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Backward_Paging_NET8_0.md Snapshot for nested OrderBy in predicate backward paging (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Backward_Paging_NET9_0.md Snapshot for nested OrderBy in predicate backward paging (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_Predicate_With_Nested_OrderBy_Backward_Paging_NET10_0.md Snapshot for nested OrderBy in predicate backward paging (NET10).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Forward_Paging_NET8_0.md Snapshot for nested OrderBy inside sort key forward paging (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Forward_Paging_NET9_0.md Snapshot for nested OrderBy inside sort key forward paging (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Forward_Paging_NET10_0.md Snapshot for nested OrderBy inside sort key forward paging (NET10).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Second_Page_NET8_0.md Snapshot verifying second-page keyset predicate translation (NET8).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Second_Page_NET9_0.md Snapshot verifying second-page keyset predicate translation (NET9).
src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/snapshots/PagingHelperTests.QueryContext_OrderKey_With_Nested_OrderBy_Second_Page_NET10_0.md Snapshot verifying second-page keyset predicate translation (NET10).
src/GreenDonut/src/GreenDonut.Data/Expressions/QueryChainVisitor.cs Introduces a base visitor that skips lambda bodies during traversal.
src/GreenDonut/src/GreenDonut.Data/Cursors/CursorKeyParser.cs Switches cursor key parsing visitor to QueryChainVisitor to avoid nested lambda traversal.
src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ReverseOrderExpressionRewriter.cs Switches reverse-order rewriter to QueryChainVisitor so only top-level order ops are rewritten.
src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExtractOrderPropertiesVisitor.cs Refines order property extraction to avoid collecting from nested lambdas/intermediate results.
src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs Updates order-by removal rewriter to rely on chain-only traversal rather than projection state tracking.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Tommsy64 Tommsy64 changed the title Fix/8262 orderby props nested selector Fix #8262 ToPageAsync() attempting to select invalid properties Jul 1, 2026
@Tommsy64

Copy link
Copy Markdown
Contributor Author

I merged in the latest main and resolved the resolved the xUnit v3 and net11 migration.

Seeing #9955 made me realize there's a related edge case this PR doesn't cover: #9955 handles null member-access paths in the keyset WHERE clause, but computed keys (e.g. t => t.Products.OrderBy(p => p.Price).First().Price) are still treated as non-nullable, so over an empty collection the row gets dropped from later pages and cursor creation throws.

Should this be addressed here or in a follow-up PR?

@michaelstaib

Copy link
Copy Markdown
Member

I will have a look at it

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.

ToPageAsync() (QueryHelpers.EnsureOrderPropsAreSelected) attempts to select Order properties that it should not

3 participants