Skip to content

CMTK0009: a Func or a method group passed to FirstOrNone, SingleOrNone or FirstOrError on a query reads every row #129

Closed

Description

@CaffeinatedCoder

FirstOrNone, SingleOrNone and FirstOrError on an IQueryable<T> bind to the query's overload only when the condition is a lambda written at the call. A Func<T, bool> held in a variable, or a method group, binds to the IEnumerable<T> overload: EF reads every row and the condition runs in memory, with no warning. QueryOptionTests.A_Func_or_a_method_group_reads_every_row_and_runs_the_condition_in_memory pins this, and its summary says "No analyzer tells it yet". The roadmap lists it under "Gaps, in any 2.x".

Proposal

CMTK0009, a Warning, reports such a call: the receiver's type is an IQueryable<T> and the chosen overload is the sequence one, because the argument is a delegate rather than an expression. A Warning may learn in a minor version (decision 34). CMTK0009's title speaks of a call that returns a default instance, so this case gets its own message, naming the overload it fell to and the row read it causes. LastOrNone and LastOrError given a delegate on a query read every row the same way and are reported too (found by variant analysis). No code fix: see the comment below.

Done when

  • The analyzer reports a Func variable, a field, a method group and a delegate-returning call, and stays silent for a lambda at the call, an Expression<Func<T, bool>> variable, and a sequence that is not a query; the receiver shapes come from the variant catalogues.
  • A code fix is considered and either added or ruled out in the issue.
  • The Analyzers README's CMTK0009 section and the EF README say so, and the test's summary no longer says no analyzer tells it.

Activity

  1. added this to the 2.3.0 milestone on Oct 29, 2026
  2. CaffeinatedCoder commented on Oct 29, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Code fix: ruled out. CMTK0009's query condition ships without one, because no mechanical rewrite is correct:

    • A Func variable, field, property or a call that returns one has its body somewhere else, often built at run time. Making it an expression means changing its declaration and every other use, which is the author's call.
    • A method group could be wrapped as x => IsGamma(x). That binds to the query's overload, but the provider then has to translate a call to the application's method. EF Core can't, so a silent full read becomes an exception when the query runs.
    • AsEnumerable() before the call is the one rewrite that keeps behaviour. It only makes the full read explicit and silences the warning. Offering it from the light bulb would make the wrong answer the easiest one.

    The Analyzers README's CMTK0009 section says there is no fix and why, and names AsEnumerable() as the way to read every row on purpose.

    Scope note: by variant analysis, the rule also reports LastOrNone and LastOrError given a delegate on a query. Their query overloads refuse to compile with a lambda ([Obsolete(error: true)]), but a delegate binds to the sequence overload and reads every row the same way. Their message suggests ordering the query the other way and taking the first.

  3. CaffeinatedCoder commented on Oct 29, 2026

    @CaffeinatedCoder
    OwnerAuthor

    Done in #137, merged into release/2.3.0 (4b4f0c6). No code fix, as the comment above says.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/option-resultOption, Result and their extensionsenhancementNew feature or requestpackage/analyzersAffects CodoMetis.TypeKit.Analyzerspackage/efcoreAffects CodoMetis.TypeKit.EntityFrameworkCore

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions