S3L0001: Unpack the query¶
A long LINQ chain or a lambda nested in lambdas is unpacked into named steps.
| Category | Snail |
| Severity | warning — an error under TreatWarningsAsErrors, which every Snail project builds with |
| Analyzer | QueryShape in Snail.Sdk.Analyzers |
| Code fix | none — naming the steps needs words only the author has |
What it reports¶
- A call chain of more than 4 LINQ operators whose receiver is a sequence:
IEnumerable<T>,IQueryable<T>,IAsyncEnumerable<T>orIObservable<T>, or a type implementing one (arrays, lists and strings included). An operator is a call named like one of LINQ's —Select,Where,OrderBy,GroupBy,Skip,Take,Any,Count,First,ToList,ToDictionary,Distinct,Append,Containsand the rest of the list in QueryShape.cs. The finding is on the outermost call, once per chain:5 operators in one chain: name the steps of this query with intermediate variables. - A lambda nested 2 deep: the third level of
a => b => c => …is reported,a lambda nested 2 deep.
What it leaves alone¶
A chain of 4 operators or fewer, and any chain whose receiver is not a sequence: a StringBuilder's Append calls, a
builder, a fluent API. Generated code is not analysed.
Why¶
A long chain reads as one breath: the reader has to run it in their head to know what each stage holds. A named variable per step says it, and gives the debugger a place to stop.
Measured across the fleet on 2026-10-12: 1.1 % of chains in src pass four operators and 0.6 % of lambdas
nest two deep, so the threshold sits above the habit and reports the outliers.
How to fix¶
// Reported
var names = orders.Where(order => order.IsPaid).OrderBy(order => order.Date).Select(order => order.Customer).Distinct().Take(10).ToList();
// Holds
var paid = orders.Where(order => order.IsPaid).OrderBy(order => order.Date);
var customers = paid.Select(order => order.Customer).Distinct();
var names = customers.Take(10).ToList();
A nested lambda moves into a named local function or method.
Suppressing it¶
Fix the code rather than silence the rule: the rule is part of the standard every Snail repository keeps. Where one place truly must differ, suppress that place and say why:
[SuppressMessage("Snail", "S3L0001:Unpack the query", Justification = "<why this place differs>")]
A NoWarn in a project or props file would hide the rule from every new line too; the
conformance check reports one as migration debt (S3LC0017).