SyntaxStudy
Sign Up
C# LINQ Join and Advanced Queries
C# Beginner 1 min read

LINQ Join and Advanced Queries

LINQ's `Join` operator performs an inner join between two sequences on matching keys, similar to SQL's INNER JOIN. `GroupJoin` produces a left outer join, returning all elements from the left sequence with a (possibly empty) collection of matching elements from the right. `SelectMany` flattens one level of nesting — it is the equivalent of a SQL cross join or Python's list comprehension over nested iterables. It is especially useful when each element of a collection has its own sub-collection that you want to query as a flat sequence. Deferred execution means the LINQ pipeline is stored as a chain of delegates and is not executed until iteration begins. If the source changes between building and executing the query, the query sees the updated source. Calling `ToList()` or `ToArray()` materialises the results at that instant, capturing a snapshot.
Example
// Join, GroupJoin, SelectMany, and deferred execution

record Customer(int Id, string Name, string Country);
record Order(int Id, int CustomerId, decimal Amount);

var customers = new List<Customer>
{
    new(1, "Alice", "UK"),
    new(2, "Bob",   "US"),
    new(3, "Carol", "UK"),
    new(4, "Dave",  "CA"),   // no orders
};

var orders = new List<Order>
{
    new(101, 1, 250m),
    new(102, 1, 400m),
    new(103, 2, 150m),
    new(104, 3, 600m),
    new(105, 3, 200m),
};

// Inner join
var joined = customers.Join(
    orders,
    c => c.Id,
    o => o.CustomerId,
    (c, o) => new { c.Name, o.Amount });

foreach (var item in joined)
    Console.WriteLine($"{item.Name}: {item.Amount:C}");

// GroupJoin (left outer join) — includes Dave with empty orders
var withOrders = customers.GroupJoin(
    orders,
    c => c.Id,
    o => o.CustomerId,
    (c, os) => new { c.Name, Total = os.Sum(o => o.Amount), Count = os.Count() });

foreach (var row in withOrders)
    Console.WriteLine($"{row.Name,-8} {row.Count} orders, total {row.Total:C}");

// SelectMany — flatten per-customer orders
var allLines = customers.SelectMany(
    c => orders.Where(o => o.CustomerId == c.Id),
    (c, o) => $"{c.Name} - Order#{o.Id} - {o.Amount:C}");

foreach (var line in allLines) Console.WriteLine(line);

// Deferred execution demo
var source = new List<int> { 1, 2, 3 };
var query  = source.Where(n => n > 1);  // not yet evaluated
source.Add(4);                           // modify source
Console.WriteLine(string.Join(",", query)); // 2,3,4 — sees the new element