Collections & LINQ · Lesson 28

Joins and one-to-many matches

Predict the number of report rows before joining two keyed collections.

How it works

A join connects elements from two sequences when selected keys compare equal. It is useful when one collection holds reference information and another holds repeated observations, such as customers and orders. The result selector decides which fields belong in each combined row. A join is therefore both a matching operation and a projection into a report shape.

An inner join produces one row for every matching pair. A left element with no match contributes no result, while multiple matching right elements produce multiple rows. If both sides contain duplicate keys, the number of results can multiply. Predicting this cardinality is an essential part of correctness; a join that compiles can still double a financial total if the key contract is wrong.

Walk through the example. Two customers are joined to three orders. Ada has two orders, Sam has one, and the output contains three rows. The customer name is repeated deliberately for Ada because each order is a separate matching observation. Ordering by customer code and order number produces a stable report before the combined values are formatted.

Watch the boundary. Do not use a join to assume uniqueness that the input has never validated. If the reference collection contains two rows for one supposedly unique customer code, each corresponding order will match both. Enforce uniqueness when loading reference data, or group and resolve duplicates explicitly. Also decide how unmatched observations should be reported, since an inner join omits them.

Practice independently. Build a product-name lookup and a sales collection, then join them into detail rows. Add two sales for one product, an unknown product code, and a duplicate reference product. Write the expected result count for every case before running it. Finally produce a separate unmatched-sales report so missing reference data cannot silently disappear from the workflow.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

var customers = new[] { (Code: "A", Name: "Ada"), (Code: "S", Name: "Sam") };
var orders = new[] { (Customer: "A", Number: 1), (Customer: "A", Number: 2), (Customer: "S", Number: 3) };
var report = customers.Join(orders, c => c.Code, o => o.Customer,
    (customer, order) => (customer.Code, customer.Name, order.Number));
foreach (var row in report.OrderBy(x => x.Code, StringComparer.Ordinal).ThenBy(x => x.Number))
    Console.WriteLine($"{row.Name}:{row.Number}");

Expected output

Ada:1
Ada:2
Sam:3
Try this
A join returns every matching pair; validate key uniqueness before trusting aggregate totals.