The question was simple: does any roof segment on any residential building have gable steel enabled?
The data was nested. A revision had residential buildings, and each building had its own collection of roof-segment inputs. I started mixing SelectMany and Any together and ended up asking one operator to do the other one’s job.
The order was the problem
I had put Any inside the selector passed to SelectMany. But Any returns a Boolean. SelectMany needs a collection from each source item so it can flatten those collections.
The correction was to flatten the child collections first, then ask whether any item matched. Here’s the same pattern with generic project and task objects:
using System.Linq;
bool hasBlockedTask = projects
.SelectMany(project => project.Tasks)
.Any(task => task.IsBlocked);
I can read that in two steps: take the tasks from every project, then look for a blocked task. The result is one Boolean for the entire collection.
There’s another readable way to say it
For this particular yes-or-no question, nested Any calls express the same check:
using System.Linq;
bool hasBlockedTask = projects
.Any(project => project.Tasks
.Any(task => task.IsBlocked));
This version asks whether there’s a project containing a blocked task. The flattened version is useful when I want to keep working with the tasks as one sequence. Neither example is trying to define behavior for null collections; both assume the input collections are initialized.
Check what comes out of each step
The part I want to remember is the intermediate result. After selecting a building’s roof segments, I still have a collection. After calling Any, I have an answer.
That’s an easy distinction to lose in a long expression, especially when the domain names are already lengthy. Breaking the query across lines and thinking about the type at each step made the mistake much easier to see.
This wasn’t a big rewrite. It was a small correction to a real question in the building model, and it’s the kind of thing I want these notes to capture.
Reference: Microsoft’s LINQ projection operations.