How to perform a left outer join using linq extension methods in C#?

In LINQ, a Left Outer Join includes all elements from the left collection and matching elements from the right collection. When there's no match in the right collection, the result still includes the left element with null values for the right side.

This is different from an Inner Join, which only includes matching elements from both collections. Left Outer Join ensures that no records from the left collection are lost, even when they don't have corresponding matches in the right collection.

Syntax

The Left Outer Join pattern using LINQ extension methods follows this structure −

leftCollection
  .GroupJoin(rightCollection,
    leftKey => leftKey.Property,
    rightKey => rightKey.Property,
    (left, rightGroup) => new { left, rightGroup })
  .SelectMany(x => x.rightGroup.DefaultIfEmpty(),
    (combined, right) => new {
      LeftProperty = combined.left.Property,
      RightProperty = right?.Property ?? "Default Value"
    })

Left Outer Join vs Inner Join Left Outer Join All from left + matching from right Mark - IT Steve - HR Mary - No Dept Inner Join Only matching from both sides Mark - IT Steve - HR Mary excluded

Example

Consider the following Employee and Department classes. Notice that Employee Mary does not have a department assigned (DepartmentID is 0). A Left Outer Join will include her record with "No Department", while an Inner Join would exclude her −

using System;
using System.Collections.Generic;
using System.Linq;

public class Employee {
    public int ID { get; set; }
    public string Name { get; set; }
    public int DepartmentID { get; set; }
    
    public static List<Employee> GetAllEmployees() {
        return new List<Employee>() {
            new Employee { ID = 1, Name = "Mark", DepartmentID = 1 },
            new Employee { ID = 2, Name = "Steve", DepartmentID = 2 },
            new Employee { ID = 3, Name = "Ben", DepartmentID = 1 },
            new Employee { ID = 4, Name = "Philip", DepartmentID = 1 },
            new Employee { ID = 5, Name = "Mary", DepartmentID = 0 }
        };
    }
}

public class Department {
    public int ID { get; set; }
    public string Name { get; set; }
    
    public static List<Department> GetAllDepartments() {
        return new List<Department>() {
            new Department { ID = 1, Name = "IT" },
            new Department { ID = 2, Name = "HR" }
        };
    }
}

static class Program {
    static void Main(string[] args) {
        var result = Employee.GetAllEmployees()
            .GroupJoin(Department.GetAllDepartments(),
                e => e.DepartmentID,
                d => d.ID,
                (emp, depts) => new { emp, depts })
            .SelectMany(z => z.depts.DefaultIfEmpty(),
                (a, b) => new {
                    EmployeeName = a.emp.Name,
                    DepartmentName = b == null ? "No Department" : b.Name
                });
                
        Console.WriteLine("Employee Name\tDepartment Name");
        Console.WriteLine("=================================");
        foreach (var v in result) {
            Console.WriteLine(v.EmployeeName + "\t\t" + v.DepartmentName);
        }
    }
}

The output of the above code is −

Employee Name	Department Name
=================================
Mark		IT
Steve		HR
Ben		IT
Philip		IT
Mary		No Department

How It Works

The Left Outer Join works in two steps −

  • GroupJoin − Groups matching departments for each employee. If no department matches, an empty group is created.

  • SelectMany with DefaultIfEmpty − Flattens the grouped results. DefaultIfEmpty() ensures that employees with no matching departments still appear in the result with a null department.

Using Method Syntax vs Query Syntax

The same Left Outer Join can be expressed using query syntax −

using System;
using System.Collections.Generic;
using System.Linq;

static class Program {
    static void Main(string[] args) {
        var employees = Employee.GetAllEmployees();
        var departments = Department.GetAllDepartments();
        
        var queryResult = from emp in employees
                         join dept in departments on emp.DepartmentID equals dept.ID into deptGroup
                         from d in deptGroup.DefaultIfEmpty()
                         select new {
                             EmployeeName = emp.Name,
                             DepartmentName = d?.Name ?? "No Department"
                         };
        
        Console.WriteLine("Using Query Syntax:");
        foreach (var item in queryResult) {
            Console.WriteLine(item.EmployeeName + " - " + item.DepartmentName);
        }
    }
}

public class Employee {
    public int ID { get; set; }
    public string Name { get; set; }
    public int DepartmentID { get; set; }
    
    public static List<Employee> GetAllEmployees() {
        return new List<Employee>() {
            new Employee { ID = 1, Name = "Mark", DepartmentID = 1 },
            new Employee { ID = 2, Name = "Steve", DepartmentID = 2 },
            new Employee { ID = 3, Name = "Ben", DepartmentID = 1 },
            new Employee { ID = 4, Name = "Mary", DepartmentID = 0 }
        };
    }
}

public class Department {
    public int ID { get; set; }
    public string Name { get; set; }
    
    public static List<Department> GetAllDepartments() {
        return new List<Department>() {
            new Department { ID = 1, Name = "IT" },
            new Department { ID = 2, Name = "HR" }
        };
    }
}

The output of the above code is −

Using Query Syntax:
Mark - IT
Steve - HR
Ben - IT
Mary - No Department

Conclusion

Left Outer Join in C# LINQ combines GroupJoin with SelectMany and DefaultIfEmpty() to include all records from the left collection, even when there are no matches in the right collection. This ensures no data is lost from the primary collection during the join operation.

Updated on: 2026-03-17T07:04:36+05:30

2K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements