Skip to main content
SEMastery
Data Accessintermediate

What's New in EF Core 10: LeftJoin and RightJoin in LINQ

Learn the new LeftJoin and RightJoin LINQ operators in EF Core 10. Simple examples, SQL mapping, and clear tables to help you write cleaner join queries.

12 min readUpdated June 1, 2026

Joining two tables is one of the most common things we do with a database. EF Core 10 makes one kind of join, the left join, much cleaner to write. It also adds a matching right join. This post walks through both, with simple words and small examples.

A everyday story first

Imagine your school is taking a class photo. The teacher has a list of every student. The teacher also has a list of clubs, like the chess club and the music club.

Now the teacher wants one big list: every student, and the club they belong to. But here is the catch. Some students have not joined any club yet.

If you only keep students who are in a club, you lose the others. That is not fair. The teacher wants all students on the list. For students with no club, the club column just stays empty.

That "keep everyone on the left, fill empty where there is no match" idea is exactly a left join. The students are the left list. The clubs are the right list. Students with no club still appear, with a blank club.

A right join is the mirror image. Keep every club, even clubs that no student joined yet, and fill the student column with a blank when nobody matches.

A left join keeps every student and shows their club, or a blank when they have none.

The old way was painful

Before EF Core 10, LINQ had no plain LeftJoin method. To get a left join you had to chain three things together: GroupJoin, then SelectMany, then DefaultIfEmpty. Many developers had to look this up every single time. It was easy to get wrong.

Here is what that old pattern looked like.

// The old way (still works, but hard to read)
var query = context.Students
    .GroupJoin(
        context.Departments,
        student => student.DepartmentId,
        department => department.Id,
        (student, departments) => new { student, departments })
    .SelectMany(
        x => x.departments.DefaultIfEmpty(),
        (x, department) => new
        {
            x.student.FirstName,
            x.student.LastName,
            Department = department.Name
        });

Read that again. It is a lot. The word "left join" does not even appear. A new teammate reading this has to decode it before they understand it. That is the problem EF Core 10 fixes.

The new way is short and clear

EF Core 10 adds a real LeftJoin operator. It reads almost like a plain English sentence, and it builds the same SQL underneath.

// The new way in EF Core 10
var query = context.Students
    .LeftJoin(
        context.Departments,
        student => student.DepartmentId,   // key on the left side
        department => department.Id,        // key on the right side
        (student, department) => new        // shape the result
        {
            student.FirstName,
            student.LastName,
            Department = department != null ? department.Name : "No department"
        });

Look at the four parts of LeftJoin:

  1. The right collection to join with (context.Departments).
  2. The key on the left side (student.DepartmentId).
  3. The key on the right side (department.Id).
  4. A result shaper that builds the final object.

Notice the department != null check. Because a left join can have no match, the right side may be null. You handle that in the result shaper, just like you would in real life when a student has no department.

How LeftJoin reads

Right table
Left key
Right key
Result

Steps

1

Right table

context.Departments

2

Left key

student.DepartmentId

3

Right key

department.Id

4

Result

new { ... }

The four arguments map cleanly to a single SQL LEFT JOIN.

What SQL does EF Core build?

This is the best part. The new LeftJoin is not magic that runs slowly in memory. EF Core translates it into a proper LEFT JOIN in SQL, sent to your database. It is the same SQL the old pattern produced.

// Your EF Core 10 LINQ
var query = context.Students
    .LeftJoin(
        context.Departments,
        s => s.DepartmentId,
        d => d.Id,
        (s, d) => new { s.FirstName, Dept = d.Name });

That becomes roughly this SQL:

SELECT [s].[FirstName], [d].[Name] AS [Dept]
FROM [Students] AS [s]
LEFT JOIN [Departments] AS [d] ON [s].[DepartmentId] = [d].[Id]

Clean LINQ in, clean SQL out. No performance loss. Your database does the join, which is what databases are good at.

The journey from your C# code to a SQL statement at the database.

RightJoin too

EF Core 10 also adds RightJoin. It keeps every row from the right collection, and only the matching rows from the left. Where there is no match, the left side is null.

// RightJoin keeps every department, even empty ones
var query = context.Students
    .RightJoin(
        context.Departments,
        student => student.DepartmentId,
        department => department.Id,
        (student, department) => new
        {
            Student = student != null ? student.FirstName : "No student yet",
            Department = department.Name
        });

This translates to a RIGHT JOIN in SQL. In real apps, right joins are used less often than left joins, because you can usually flip the two collections and write a left join instead. But it is nice to have the choice, and sometimes a right join expresses your intent more directly.

Left join vs right join at a glance

Here is a small table to keep the two straight in your head.

FeatureLeftJoinRightJoin
Keeps all rows fromLeft collectionRight collection
Fills null when no match onRight sideLeft side
SQL producedLEFT JOINRIGHT JOIN
Most common in real appsYes, very commonLess common
Available in EF Core 10YesYes

Old pattern vs new operator

This table shows why the change matters so much for daily work.

PointOld patternNew LeftJoin
Methods neededGroupJoin + SelectMany + DefaultIfEmptyJust LeftJoin
ReadabilityHard, needs decodingEasy, reads like SQL
The words "left join" appearNoYes
SQL producedLEFT JOINLEFT JOIN (identical)
PerformanceSameSame
Risk of mistakesHigherLower

Choosing a join

Need all of A?
Need all of B?
Inner join

Steps

1

Need all of A?

Use LeftJoin with A on left

2

Need all of B?

Use RightJoin or flip to LeftJoin

3

Inner join

Use Join for matches only

A quick way to pick the right join for your query.

Inner join, left join, right join

It helps to see all three side by side. An inner join keeps only rows that match on both sides. A left join keeps everything on the left. A right join keeps everything on the right.

The three join types and which rows survive each one.

Think back to the class photo. An inner join is "only students who are in a club". A left join is "all students, club shown if any". A right join is "all clubs, student shown if any". Same data, different rules about who stays.

What you need to use it

A few simple rules to remember.

  • You need .NET 10 and EF Core 10. These operators do not exist on older .NET versions or on .NET Framework.
  • The SQL Server, SQLite, and PostgreSQL (Npgsql) providers all support them.
  • For now, LeftJoin and RightJoin are method syntax only. There is no new query keyword. So you cannot write them inside a from ... select ... query expression yet. You call them as methods.
  • Always handle the possible null on the optional side in your result shaper.

A fuller example

Let's put it together with a tiny model. Say we track blog posts and their authors. Some posts were imported and have no author linked. We still want to list every post.

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int? AuthorId { get; set; }   // nullable: some posts have no author
}
 
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
 
// Query: every post, with author name or a fallback
var postsWithAuthors = await context.Posts
    .LeftJoin(
        context.Authors,
        post => post.AuthorId,
        author => author.Id,
        (post, author) => new
        {
            post.Title,
            AuthorName = author != null ? author.Name : "Unknown author"
        })
    .ToListAsync();

Every post shows up. Posts without an author show "Unknown author". The query is short, and a teammate understands it at first glance. That readability is the real win here.

A small tip on null handling

The optional side of a left join can be null. If you forget to check, you can get a NullReferenceException when your code touches a property like author.Name. So keep the habit: in the result shaper, use a null check or the null-conditional operator. For a route like GET /posts/{id} that returns this shaped data, returning a clean fallback string is friendlier to the caller than crashing.

A second real example: orders and customers

Joins are everywhere in business apps. Let's say a shop has customers and orders. The boss wants a report of every customer, and how many orders each one placed. Some customers signed up but never bought anything. The boss still wants to see them, with a count of zero.

This is a left join again. Customers are the left side. We keep them all. Orders are the right side. Customers with no orders still show up.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
 
public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public decimal Total { get; set; }
}
 
// Every customer, with the order if any
var report = await context.Customers
    .LeftJoin(
        context.Orders,
        customer => customer.Id,
        order => order.CustomerId,
        (customer, order) => new
        {
            customer.Name,
            OrderId = order != null ? order.Id : (int?)null,
            Spent = order != null ? order.Total : 0m
        })
    .ToListAsync();

A customer named "Asha" who never ordered still appears in the report, with OrderId as null and Spent as 0. A customer who placed three orders shows up three times, once per order. That is normal join behavior. If you want one row per customer with a total, you would group the result afterwards.

Common mistakes to avoid

A few traps catch people when they first use these operators. Keep this list handy.

  • Forgetting the null check. The right side of a left join can be null. Touching a property on it without a check throws a NullReferenceException. Always guard it in the result shaper.
  • Trying to use query syntax. You cannot write LeftJoin inside a from x in ... select x block yet. Use method syntax: context.A.LeftJoin(context.B, ...).
  • Running on an old .NET version. If your project still targets .NET 8 or 9, the method is not there. Check that your TargetFramework is net10.0.
  • Mixing up the key order. The third argument is the left key, the fourth is the right key. Swapping them gives wrong or empty results.
  • Expecting one row per left item. A left join still produces one row per matching right item. If a customer has three orders, you get three rows.

Debugging an empty result

Check keys
Check framework
Check nulls

Steps

1

Check keys

Left key vs right key order

2

Check framework

net10.0 target?

3

Check nulls

Guard the right side

Steps to check when your left join returns nothing useful.

Why this matters for teams

Code is read far more often than it is written. The old GroupJoin and DefaultIfEmpty pattern was correct, but it slowed people down. Every reader had to pause and translate it in their head. Junior developers often copied it without fully understanding it.

The new LeftJoin operator removes that friction. The query now says what it means. A person who knows SQL can read the LINQ and instantly see the LEFT JOIN. That shared understanding makes code reviews faster and bugs rarer. Small wins like this add up across a big codebase.

References and further reading

Quick recap

  • A left join keeps every row from the left collection, and fills the right side with null when there is no match.
  • A right join does the mirror: keep every row from the right collection.
  • Before EF Core 10 you had to chain GroupJoin, SelectMany, and DefaultIfEmpty. It worked but was hard to read.
  • EF Core 10 adds plain LeftJoin and RightJoin operators that read like SQL.
  • They translate to the same LEFT JOIN and RIGHT JOIN SQL, so there is no performance loss.
  • You need .NET 10 and EF Core 10; they are method syntax only for now.
  • Always handle the possible null on the optional side of the join.

Related Posts