Analyzer rule
Spec: LC005 - Multiple OrderBy Calls
EF Core LINQ performance analyzer and Roslyn analyzer for catching query issues at compile time.
Spec: LC005 - Multiple OrderBy Calls
Goal
Detect usage of multiple OrderBy or OrderByDescending calls in a single query chain.
The Problem
In LINQ, each OrderBy call completely resets the sort order. If you want to sort by multiple columns, you must use ThenBy or ThenByDescending after the first OrderBy.
Example Violation
// Error: The first sort by Name is discarded and ignored.
var users = db.Users.OrderBy(u => u.Name).OrderBy(u => u.Age).ToList();
// Same reset after a simple local hop.
var sorted = db.Users.OrderBy(u => u.Name);
var users = sorted.OrderBy(u => u.Age).ToList();
The Fix
Use ThenBy or ThenByDescending for the second and later sort keys.
// Correct: Sorts by Name, then by Age for ties.
var users = db.Users.OrderBy(u => u.Name).ThenBy(u => u.Age).ToList();
The code fix rewrites only the later resetting sort call, preserving the selector and any explicit generic type arguments. The same fix is offered when the resetting sort is called on a single-assignment local that still has an ordered type:
var sorted = db.Users.OrderBy(u => u.Name);
var users = sorted.ThenBy(u => u.Age).ToList();
If the local is explicitly widened to IEnumerable<T> or IQueryable<T>, LC005 still reports the reset, but the fix is
manual because ThenBy is not available on the widened receiver type. LC005 stays quiet when the local is reassigned
before the later OrderBy directly, through deconstruction, or through out/ref, because the analyzer cannot prove which ordering state
reaches the reset.
Query-comprehension syntax
Two separate orderby clauses are the query-syntax spelling of the same reset and are flagged:
// Warning: the second orderby resets the first.
var users = from u in db.Users
orderby u.Name
orderby u.Age
select u;
A single orderby clause with multiple keys is correct multi-level sorting (it lowers to
OrderBy(...).ThenBy(...)) and is intentionally left quiet:
// Correct: sorts by Name, then by Age for ties.
var users = from u in db.Users
orderby u.Name, u.Age
select u;
The diagnostic is report-only for query syntax — there is no method-call node to rewrite to
ThenBy, so the automatic fix is withheld there. Collapse the two clauses into one comma-separated
orderby to resolve it by hand. The ThenBy fix is still offered for the fluent
OrderBy(...).OrderBy(...) form.
Boundaries
LC005 follows direct fluent chains, static Enumerable/Queryable syntax, and single-assignment sorted locals. It does
not chase fields, properties, multi-assignment locals, or arbitrary helper methods, because those shapes need broader
dataflow proof to avoid noisy warnings.