- Home
- .NET tutorials
- How LeftJoin and RightJoin Work in EF Core .NET 10
How LeftJoin and RightJoin Work in EF Core .NET 10
Published: Monday 9 February 2026
Entity Framework Core has just fixed one of its biggest limitations in .NET 10.
Example
For this example, we are going to have a Product table that has an optional join on the CategoryId field against the Category table's Id field.
Product
- Id
- Name
- CategoryId
Category
- Id
- Name
We have added these as entities to an ASP.NET Core Web API, with a Category navigation property on the Product entity:
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int? CategoryId { get; set; }
public Category? Category { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
We have added a DbSet for both Products and Categories in the DbContext:
public class EFCoreDbContext : DbContext
{
public required DbSet<Product> Products { get; set; }
public required DbSet<Category> Categories { get; set; }
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)
{
}
}
Before .NET 10
If you have a simple join and you are using navigation properties, you can use the Include method.
public async Task<ProductWithCategoryDto?> GetWithCategories(int id)
{
return await _dbContext.Products
.Include(p => p.Category)
.Where(p => p.Id == id)
.Select(p => new ProductWithCategoryDto(
p.Id,
p.Name,
p.Category != null ? p.Category.Id : null,
p.Category != null ? p.Category.Name : null
))
.FirstOrDefaultAsync();
}
However, if your join was more complex, you had to use the GroupJoin method. Here, you needed to supply the Categories DbSet from the DbContext, specify the join properties, then call SelectMany and DefaultIfEmpty to create a left join. You could then select the type you wished to return.
public async Task<ProductWithCategoryDto?> GetWithCategories(int id)
{
return await _dbContext.Products
.GroupJoin(
_dbContext.Categories,
p => p.CategoryId,
c => c.Id,
(product, category) => new { Product = product, Category = category }
)
.Where(p => p.Product.Id == id)
.SelectMany(
join => join.Category.DefaultIfEmpty(),
(join, category) => new ProductWithCategoryDto(
join.Product.Id,
join.Product.Name,
category != null ? category.Id : null,
category != null ? category.Name : null
)
)
.FirstOrDefaultAsync();
}
Introducing LeftJoin and RightJoin
.NET 10 introduces the new LeftJoin and RightJoin methods when using LINQ. With the LeftJoin method, you supply the DbSet for the table you are joining, the two properties to join on, and what is being returned.
public async Task<ProductWithCategoryDto?> GetWithCategories(int id)
{
return await _dbContext.Products
.Where(p => p.Id == id)
.LeftJoin(
_dbContext.Categories,
product => product.CategoryId,
category => category.Id,
(product, category) => new ProductWithCategoryDto(
product.Id,
product.Name,
category != null ? category.Id : null,
category != null ? category.Name : null
)
)
.FirstOrDefaultAsync();
}
A right join is the opposite of a left join, where you query the second table and then join the first table to it.
public async Task<IList<CategoryWithProductDto>> GetWithProducts(int id)
{
return await _dbContext.Categories
.Where(c => c.Id == id)
.RightJoin(
_dbContext.Products,
category => category.Id,
product => product.CategoryId,
(product, category) => new CategoryWithProductDto(
category.Id,
category.Name,
product != null ? product.Id : null,
product != null ? product.Name : null
)
)
.ToListAsync();
}
Does it work with query syntax?
No, unfortunately it does not. You still have to use DefaultIfEmpty when using query syntax:
public async Task<ProductWithCategoryDto?> GetWithCategories(int id)
{
return await (
from p in _dbContext.Products
join c in _dbContext.Categories
on p.CategoryId equals c.Id into pc
from c in pc.DefaultIfEmpty() // LEFT JOIN
where p.Id == id
select new ProductWithCategoryDto(
p.Id,
p.Name,
c != null ? c.Id : (int?)null,
c != null ? c.Name : null
)
).FirstOrDefaultAsync();
}
Using Entity Framework Core in a real-world API
If you want to use Entity Framework Core in a real-world API, then our Minimal APIs for complete beginners course is just for you. You will learn how to query, insert, update, and delete records from the database, and the best way to use them in a Web API.
Watch the video
Watch this video where I go through the old ways of doing joins and show how you can use LeftJoin and RightJoin in a Web API project:
Final thoughts
It is good that Microsoft have added LeftJoin and RightJoin to LINQ. Going forward, we hope they also add support for these methods in query syntax. However, this change already makes writing complex joins in Entity Framework Core much simpler.
Related pages
.NET 10: What are the steps to update your project?
Learn how to upgrade your project to .NET 10 - update Visual Studio and projects, migrate Swagger to OpenAPI, and update your Dockerfile.