Quay lại blog
20 tháng 3, 20264 phút đọc

EF Core 10 đã gọn hơn với LeftJoin và RightJoin trong LINQ

EF Core 10 bổ sung chính thức các toán tử LeftJoin và RightJoin, giúp câu lệnh LINQ dễ đọc, rõ ý định và dễ bảo trì hơn.

.NET.NETEF CoreLINQBackend

Nếu bạn đã làm việc với cơ sở dữ liệu quan hệ đủ lâu, chắc bạn hiểu LEFT JOINRIGHT JOIN quan trọng đến mức nào.

Nhưng nếu bạn từng dùng Entity Framework Core trước .NET 10, bạn cũng sẽ nhớ cảm giác "khổ sở" ấy 😅

Viết một câu left join đơn giản trong LINQ trước đây thường rườm rà không cần thiết, phải đi qua GroupJoin, DefaultIfEmpty, rồi SelectMany. Nó vẫn chạy đúng, nhưng ý định của đoạn code lại không hề rõ ràng.

Tin vui là EF Core 10 cuối cùng đã bổ sung LeftJoinRightJoin - giúp code gần hơn nhiều với cách chúng ta tư duy.


LEFT JOIN là gì?

Một LEFT JOIN sẽ trả về:

  • Tất cả bản ghi ở bên trái
  • Các bản ghi khớp ở bên phải
  • Nếu không có dữ liệu khớp thì giá trị bên phải sẽ là null

Ví dụ đơn giản

Giả sử bạn có:

  • Customers
  • Orders

Bạn muốn hiển thị toàn bộ khách hàng, kể cả những người chưa từng đặt đơn hàng nào.


Cách cũ trước .NET 10

Để thực hiện left join, trước đây bạn thường phải viết như sau.

Query syntax

var query =
    from customer in dbContext.Customers
    join order in dbContext.Orders
        on customer.Id equals order.CustomerId into orderGroup
    from subOrder in orderGroup.DefaultIfEmpty()
    select new
    {
        CustomerId = customer.Id,
        customer.Name,
        OrderId = (int?)subOrder.Id ?? 0,
        Total = (decimal?)subOrder.Total ?? 0
    };

Method syntax

var query = dbContext.Customers
    .GroupJoin(
        dbContext.Orders,
        customer => customer.Id,
        order => order.CustomerId,
        (customer, orders) => new { customer, orders })
    .SelectMany(
        x => x.orders.DefaultIfEmpty(),
        (x, order) => new
        {
            CustomerId = x.customer.Id,
            x.customer.Name,
            OrderId = (int?)order!.Id ?? 0,
            Total = (decimal?)order!.Total ?? 0
        });

Vì sao cách này gây khó chịu?

  • Quá dài cho một thao tác rất phổ biến
  • Khó đọc vì ý định thật sự bị che khuất
  • Dễ viết sai
  • Không thân thiện với người mới

Cách mới với LeftJoin trong EF Core 10

Giờ đây chúng ta có thể viết đúng thứ mình muốn thể hiện:

var query = dbContext.Customers
    .LeftJoin(
        dbContext.Orders,
        customer => customer.Id,
        order => order.CustomerId,
        (customer, order) => new
        {
            CustomerId = customer.Id,
            customer.Name,
            OrderId = (int?)order.Id ?? 0,
            Total = (decimal?)order.Total ?? 0
        });

Vì sao đây là thay đổi đáng giá?

  • Code ngắn gọn và sạch hơn nhiều
  • Ý định thể hiện ngay lập tức
  • Không còn phải ghép GroupJoin + DefaultIfEmpty
  • SQL sinh ra phía dưới vẫn tương đương

Còn RIGHT JOIN thì sao?

EF Core 10 cũng bổ sung RightJoin.

Nó làm điều ngược lại:

  • Giữ tất cả bản ghi ở bên phải
  • Ghép dữ liệu từ bên trái nếu có

Ví dụ

Giả sử bạn muốn hiển thị toàn bộ đơn hàng, kể cả khi một vài khách hàng đã bị xóa hoặc không còn tồn tại trong dữ liệu:

var query = dbContext.Customers
    .RightJoin(
        dbContext.Orders,
        customer => customer.Id,
        order => order.CustomerId,
        (customer, order) => new
        {
            CustomerName = customer.Name,
            OrderId = order.Id,
            order.Total
        });

Lưu ý quan trọng

  • Hiện tại, query syntax kiểu from ... select chưa hỗ trợ LeftJoinRightJoin
  • Bạn cần dùng method syntax

Khi nào bạn sẽ dùng tính năng này?

Thực tế là gần như ở khắp nơi:

  • Khách hàng có thể có hoặc không có đơn hàng
  • Người dùng có hoặc không có hồ sơ chi tiết
  • Sản phẩm có hoặc không có giảm giá
  • Nhân viên có hoặc không có phòng ban

Trước đây, nhiều người thường:

  • Viết nhiều query tách rời
  • Vô tình dùng INNER JOIN và làm mất dữ liệu

Giờ thì lý do để né LEFT JOIN gần như không còn nữa.


Các thực hành tốt

  • Luôn xử lý null ở phía dữ liệu tùy chọn
order.Total ?? 0
  • Giữ projection gọn, chỉ lấy những cột cần thiết
  • Thêm index cho join key để tối ưu hiệu năng

Kết luận

Đây là một trong những tính năng nhỏ nhưng tạo khác biệt lớn trong trải nghiệm code hằng ngày.

Với LeftJoinRightJoin:

  • Code sạch hơn
  • Ý định rõ ràng hơn
  • Ít khả năng sinh bug hơn

Cuối cùng thì LINQ cũng đã tiến gần hơn tới SQL, nhưng không còn mang theo cảm giác đau đầu như trước.

Support My Work

If you've enjoyed my content and found it helpful, consider buying me a coffee. It keeps me caffeinated and creating!