LINQ in Practice · Lesson 20

Lookup indexes one-to-many relationships

Build an index when each key can intentionally have several values.

How it works

A dictionary maps a key to one value, while a lookup maps a key to a sequence of values. Employee teams are naturally one-to-many: Engineering can contain several employees. ToLookup builds that grouping index from the source, allowing repeated queries by team without writing a manual dictionary of lists.

The Engineering lookup contains two rows, and their names are printed for inspection. Querying a key absent from the lookup returns an empty sequence, so the missing count is zero. This is a useful contract when the caller treats no members as an ordinary empty result rather than a missing-key exception.

ToLookup evaluates the source to build its index. It should not be confused with a live view that automatically discovers employees later appended to an unrelated source list. The rows stored in the lookup can still refer to mutable objects, however, so a grouping snapshot is not necessarily a deep snapshot of every object's contents.

The explicit ordinal comparer makes team-key equality predictable for this identifier-like sample. If the application considers engineering and Engineering the same team, choose a suitable comparer deliberately. Mixing different equality policies between the index and later membership checks can create hard-to-explain lookup failures.

Practice by building the source as a List<Employee>, creating the lookup, and then adding another Engineering employee to the list. Compare the existing lookup's count with a newly built lookup. Explain which operation materialized the grouping boundary and why a source collection mutation does not rebuild that index.

Then write a report that iterates a fixed list of expected teams, including one absent team, and prints a count for each. This makes the distinction between observed groups and required report categories explicit. Use a lookup when repeated one-to-many retrieval is the task; use a simple GroupBy pipeline when a single pass over grouped summaries is enough.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

var rows = new[] { new Employee("Engineering", "Ada"), new Employee("Engineering", "Ben"), new Employee("Design", "Cy") };
var byTeam = rows.ToLookup(row => row.Team, StringComparer.Ordinal);
Console.WriteLine(byTeam["Engineering"].Count());
Console.WriteLine(byTeam["Missing"].Count());
Console.WriteLine(string.Join(",", byTeam["Engineering"].Select(row => row.Name)));
record Employee(string Team, string Name);

Expected output

2
0
Ada,Ben
Try this
Unlike a dictionary of single values, a lookup expects repeated keys.