Skip to content

Fix paging on the node of another connection - #577

Merged
lukemurray merged 1 commit into
fix/nested-connection-paging-effrom
fix/connection-in-connection-node
Oct 10, 2026
Merged

lukemurray merged 1 commit into
fix/nested-connection-paging-effrom
fix/connection-in-connection-node

Conversation

@lukemurray

Copy link
Copy Markdown
Collaborator

Stacked on #576. This PR's base is that branch, so GitHub will retarget it to main once #576 merges.

Problem

Paging on the node of another connection failed in memory and with EF, on main as well:

{ movies(first: 2) { edges { node { name actors(first: 1) { edges { node { name } } } } } } }
Field 'movies' - unbound variable: p_ConnectionEdge`1

UseOffsetPaging() on the inner field failed the same way.

Cause and fix

  1. Wrong context. ConnectionEdgeExtension and OffsetPagingItemsExtension rebuild the collection from the paging field's original expression. They bound it to the grandparent node's NextFieldContext. For a parent that is a list or a root field, that's the context the field was compiled against. For node, it's the schema expression p_ConnectionEdge.Node, not the edge being selected, so the parameter is left unbound. Extensions are given the field's own expression as Context. So Field.GetExpression now records the context a field with extensions is bound to on the per-request CompileContext (an internal store, so no new public API). The edges/items extensions use that and fall back to the old lookup.
  2. Shared lambda parameter. Every connection of a type shares one edges field (and one ConnectionEdgeExtension). Its Select parameter edgeNode was a field on the extension. With the same connection field at two levels (actor.movies ... actors.node.movies), nested lambdas declared the same ParameterExpression. That compiles in memory, but EF fails with When called from 'VisitLambda', rewriting a node of type 'ParameterExpression' must return a non-null value of the same type. The parameter is now created per use and read back from the Select when the selection is rebuilt.

Provider note

To page a nested collection inside another paged nested collection, EF needs SQL APPLY. SQLite doesn't support it, so on SQLite that query fails with EF's Translating this query requires the SQL APPLY operation. I checked the same three query shapes against the SQL Server provider (with a server that doesn't exist): they translate, and the only errors were connection failures. That covers translation only, not results against a real database.

Tests

  • ConnectionPagingTests.TestPagingInConnectionNode (in memory): a connection and an offset-paged field on the node of a root connection, checking values, cursors and counts.
  • PagingTests.TestNestedConnectionPagingInConnectionNode (EF/SQLite): a navigation connection on the node of a root IQueryable connection, using last at both levels.
  • PagingTests.TestNestedConnectionPagingSameFieldAtTwoLevels (in memory, see the provider note): three levels with Actor.movies used twice, checking results. It also walks the compiled expression and asserts that no lambda re-declares an enclosing lambda's parameter. With the shared parameter put back temporarily, this check failed as expected.

Full suite passes on net8.0, net9.0 and net10.0.

🤖 Generated with Claude Code

{ movies(first: 2) { edges { node { actors(first: 1) { ... } } } } } with
UseConnectionPaging (or UseOffsetPaging) on the inner field failed with
"unbound variable: p_ConnectionEdge`1", in memory and with EF.

- The inner edges/items field rebuilt its collection from the grandparent
  node's NextFieldContext. For `node` that is the schema expression
  p_ConnectionEdge.Node, not the edge being selected. Field.GetExpression
  now records the context a field with extensions is bound to on the
  (per-request) CompileContext, and the edges/items extensions use it.
- The edges field (and its ConnectionEdgeExtension) is shared by every
  connection of a type, and its Select parameter was a field on the
  extension. The same connection at two levels declared it in nested
  lambdas, which EF can not rewrite ("When called from 'VisitLambda'...").
  Create it per use.

EF still needs SQL APPLY to page a nested collection inside another paged
nested collection, which SQLite does not support - the two-level case is
tested in memory with a check that no lambda shadows a parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lukemurray
lukemurray force-pushed the fix/connection-in-connection-node branch from b5e1542 to c631041 Compare October 10, 2026 18:56
@lukemurray
lukemurray merged commit 9795c5a into fix/nested-connection-paging-ef Oct 10, 2026
lukemurray added a commit that referenced this pull request Oct 10, 2026
#577 merged into the #576 branch after 6.3.0 was released, so its fix is in
6.3.1 rather than 6.3.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant