Fix #8262 ToPageAsync() attempting to select invalid properties - #9840
Conversation
|
I added a third commit that fixes a similar case when a sort key itself contains a nested 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. |
There was a problem hiding this comment.
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
QueryChainVisitorto 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.
|
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. Should this be addressed here or in a follow-up PR? |
|
I will have a look at it |
Fixes #8262 and adds associated tests.
The root cause was the paging expression visitors walking the entire query expression tree and treating every
OrderBy/ThenBythey 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.