Course contentsShow
← C#
Lesson 1082 of 1,90810. Entity Framework Core ModelingFree lesson

Optimizing Join Operations

Use explicit joins to control the shape of generated SQL versus implicit navigation loading.

In this lesson, you will learn how to shift from reliance on implicit navigation loading to explicit LINQ joins to gain precise control over your generated SQL.

The Problem with Implicit Navigation

When you use navigation properties (like order.Customer), EF Core relies on its internal model to decide how to fetch related data. While convenient, this often results in LEFT JOIN operations that include columns you don't need, or worse, Cartesian explosions when loading multiple collections. If you only need a specific subset of data from a related table, EF Core’s default Include behavior can be overly aggressive.

Taking Control with Explicit Joins

By using the LINQ Join method, you move away from the "black box" of navigation properties and force EF Core to construct a specific INNER JOIN. This is particularly useful when you want to filter or project data based on secondary tables without bringing the entire entity graph into memory.

Consider this example where we want to find orders placed by customers in a specific city:

var query = context.Orders
    .Join(context.Customers,
        order => order.CustomerId,
        customer => customer.Id,
        (order, customer) => new { order, customer })
    .Where(x => x.customer.City == "Seattle")
    .Select(x => new { x.order.Id, x.customer.Name });

Why This Optimizes Performance

  1. Precise SQL Generation: Instead of relying on EF Core to resolve the relationship through internal metadata, you explicitly define the join condition. This often leads to more compact, readable SQL that the database engine can optimize more effectively.
  2. Reduced Data Volume: By performing an explicit join, you are primed to use projections (the select statement) immediately. This prevents the "Select *" behavior that often accompanies Include, significantly reducing the amount of data transferred from the database to your application.
  3. Inner Join Default: Unlike Include, which typically defaults to LEFT JOIN to ensure no data is omitted, an explicit Join defaults to an INNER JOIN. If you know the related data must exist for your business logic, an INNER JOIN is almost always more performant at the database level.

Key Takeaway: Use explicit LINQ joins to replace broad navigation loading when you need to target specific related columns or enforce an INNER JOIN for better query performance.