VieTopikKhoá học
  • Games
  • Cá nhân
Tải app
© 2026 VieTopikChính sách bảo mậtĐiều khoản sử dụngCài appEnglish
  • Trang chủ
  • Học
  • Luyện thi
  • Cẩm nang
  • Games
  • Cá nhân
ASP.NET Core Web APIDữ liệu với EF Core
Bài 16/19
5 phút

Quan hệ một-nhiều

Bình luận
Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt

Một đơn hàng có nhiều dòng hàng: 2 bút, 3 vở. Hai loại dữ liệu này nằm ở hai bảng, nối với nhau qua mã đơn hàng. Bài này khai báo quan hệ đó bằng EF Core và lấy đơn hàng kèm các dòng hàng của nó.

Khái niệm

Quan hệ một-nhiều và khoá ngoại đã học ở bài Khoá ngoại của khoá SQL. Bài này khai báo chúng bằng class.

🧲 Navigation property: property trỏ tới object liên quan, như Order.Lines, để đi từ đơn hàng tới các dòng hàng.

Ví dụ

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public List<OrderLine> Lines { get; set; } =
        new List<OrderLine>();
}

public class OrderLine
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

public class ShopDbContext : DbContext
{
    public ShopDbContext(
        DbContextOptions<ShopDbContext> options)
        : base(options)
    {
    }

    public DbSet<Product> Products => Set<Product>();
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<OrderLine> OrderLines =>
        Set<OrderLine>();
}

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly ShopDbContext _db;

    public OrdersController(ShopDbContext db)
    {
        _db = db;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Order>> GetById(
        int id)
    {
        var order = await _db.Orders
            .Include(o => o.Lines)
            .FirstOrDefaultAsync(o => o.Id == id);
        if (order == null)
        {
            return NotFound();
        }
        return Ok(order);
    }
}
  • Order.Lines là navigation property, cho biết một đơn có nhiều dòng.
  • OrderLine.OrderId là khoá ngoại. EF Core tự nhận ra nhờ tên theo mẫu TênClass + Id.
  • Hai class tương ứng với bảng orders, order_lines của khoá SQL. Đơn chỉ lưu CustomerId, dòng hàng lưu ProductId và UnitPrice là giá lúc bán, đúng như bài Chuẩn hoá.
  • Migration tạo hai bảng ORDERS và ORDER_LINES, nối với nhau qua cột ORDER_ID.
  • Include(o => o.Lines) bảo EF Core đọc luôn các dòng hàng cùng đơn hàng. Câu SQL được sinh ra là một LEFT JOIN từ ORDERS sang ORDER_LINES.
Order int Id int CustomerId OrderLine int Id int OrderId int ProductId int Quantity decimal UnitPrice có
Một Order có nhiều OrderLine, nối qua OrderId

Thử ngay

Tạo migration và cập nhật database:

dotnet ef migrations add AddOrders
dotnet ef database update

Rồi thêm action sau vào OrdersController để tạo một đơn mẫu có hai dòng hàng:

using Microsoft.AspNetCore.Mvc;

// Thêm action này vào trong OrdersController
[HttpPost("sample")]
public async Task<IActionResult> CreateSample()
{
    var order = new Order { CustomerId = 1 };
    order.Lines.Add(new OrderLine
    {
        ProductId = 1, Quantity = 2, UnitPrice = 5000m
    });
    order.Lines.Add(new OrderLine
    {
        ProductId = 2, Quantity = 3, UnitPrice = 12000m
    });

    _db.Orders.Add(order);
    await _db.SaveChangesAsync();
    return Ok(order.Id);
}

Chạy server, tạo đơn mẫu rồi đọc lại:

curl -i -X POST http://localhost:5000/api/orders/sample
curl -i http://localhost:5000/api/orders/1

Rồi xoá dòng .Include(o => o.Lines) và gọi lại.

Đoán trước khi chạy: bỏ Include thì lines trong response là null, mảng rỗng, hay vẫn đủ hai dòng?

Xem kết quả
POST sample:  200, body là 1
Có Include:   200 {"id":1,"customerId":1,"lines":[{...},{...}]}
Bỏ Include:   200 {"id":1,"customerId":1,"lines":[]}

Mảng rỗng. Không có Include, EF Core chỉ đọc bảng ORDERS. Lines giữ giá trị khởi tạo là list rỗng, dù trong database vẫn có hai dòng hàng.

Lỗi hay gặp

Quên Include. Không báo lỗi gì, chỉ âm thầm trả về dữ liệu thiếu. Hễ cần dữ liệu liên quan thì phải Include nó.

Navigation hai chiều khi trả JSON. Thêm Order vào OrderLine để đi ngược lại, rồi trả thẳng entity ra API. Order chứa Lines, mỗi dòng lại chứa Order, JSON lặp vô tận và báo lỗi.

// SAI — OrderLine trỏ ngược về Order, JSON lặp mãi
public class OrderLine
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public Order? Order { get; set; }
}

Trả dữ liệu qua DTO như bài DTO, chỉ chọn những trường cần gửi, vòng lặp sẽ không xảy ra.

// ĐÚNG — DTO chỉ giữ OrderId, không trỏ ngược
public class LineResponse
{
    public int Id { get; set; }
    public int OrderId { get; set; }
}

Tóm tắt

  • Một-nhiều: một đơn hàng có nhiều dòng hàng.
  • Khai báo bằng navigation property (List<OrderLine> Lines) và khoá ngoại (OrderId).
  • Muốn đọc dữ liệu liên quan thì dùng Include.
  • Trả dữ liệu có quan hệ ra API qua DTO để tránh vòng lặp JSON.
Truy vấn với EF CoreXử lý lỗi tập trung

Bình luận

Đang tải bình luận…

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt