Skip to content

Query: Re-evaluate concept of pending selector in nav expansion #20291

Description

@smitpatel

When we rewrote query in 3.0, we added a concept called pending selector in nav expansion. Since nav expansion is expanding navigations, we defer applying the projection and keep intermediate shape to reuse the navigations already expanded. This is useful in many scenarios but deferring projection has side effects. See #7776 for one example. That is just one scenario. Any time FirstOrDefault is used in any way (selectmany or next select), by deferring that projection we duplicate the subquery for any property access on that. Not only it causes ugly query but potentially bad perf.

Instead of deferring the projection if we applied projection in above case then, during translation it would cause a subquery and any further property access would be columns coming out subquery. Hence it can be translated without causing client eval error at the same time avoid repeating subquery. This approach would do exactly what @maumar requested in the #7776 (comment)

Making this work will require

  • Update navigation expansion to take a hybrid approach to apply projection at the same time remember the navigations already expanded to reuse them.
    • We only need to remember navigations which can be accessible by projection being applied.
  • Update translation pipeline to handle translating query operators after client eval phase if it can be server evaluated.

There are few more associated issues this would fix. #18775

Activity

  1. self-assigned this
    on Mar 15, 2020
  2. added this to the 5.0.0 milestone on Mar 16, 2020
  3. modified the milestones: 5.0.0, Backlog on Jun 9, 2020
  4. 12 remaining items

  5. added theissue type on Jun 15, 2025
  6. self-assigned this
    on Jun 24, 2025
  7. roji commented on Jun 24, 2025

    @roji
    Member

    There are two possible approaches here:

    • More narrowly remove the pending selector feature; in other words, stop moving the Select() operator forward. As written above, we'd need to replace this with logic to ensure that expanded navigations are reused, etc. This is the smaller version of the change.
    • More broadly remove navigation expansion altogether as a universal preprocessing step, and introduce joins as part of the translation phase instead. This is a bigger change that's tracked by #32957.

    Note that whatever approach we choose here, this will most likely be a breaking change: since we currently move forward projections, it's possible to have a client-evaluated Select() in the middle of the query:

    _ = await context.Blogs.Select(b => LocalFunction()).Where(...).ToListAsync();
    
    // Nav expansion converts this to context.Blogs.Where(...).Select(b => LocalFunction()).ToListAsync()

    Such queries would no longer translate, and users would have to manually rewrite them to put the Select at the end. IMHO this is a good breaking change, as it simplifies the rules as to where we allow client evaluation (last Select only), and make it much clearer which queries perform client eval and where.

  8. modified the milestones: Backlog, 12.0.0 on Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions