Top 7 điều cần biết về Entity Framework cho dev .NET

Ở bên mình, gần như dự án backend .NET nào chúng tôi tham gia cũng có mặt Entity Framework. Đây là ORM chính thức do Microsoft phát triển, cho phép bạn thao tác dữ liệu bằng object C# thay vì phải viết SQL thủ công cho từng thao tác. Nghe thì đơn giản, nhưng nếu không hiểu EF đang làm gì phía sau, bạn rất dễ gặp chậm trễ hoặc lỗi khó hiểu khi đưa lên production.

Bài này tổng hợp bảy điều chúng tôi thấy đáng biết nhất. Mỗi điều đều được rút ra từ việc rà soát mã nguồn và xử lý sự cố thực tế, không phải danh sách ghép cho đủ số.

Entity Framework là gì và vì sao nên hiểu kỹ?

Entity Framework, hay EF, là một ORM giúp ánh xạ bảng trong cơ sở dữ liệu sang các lớp C#. Khi bạn viết một truy vấn LINQ, EF sẽ dịch nó thành câu SQL tương ứng và thực thi trên database.

Chính cơ chế dịch tự động này khiến EF tiện lợi, nhưng cũng là chỗ dễ sinh lỗi. Bạn viết code trông rất gọn, còn câu SQL thật sự chạy ra sao thì không nhìn thấy. Vì vậy, hiểu cách EF hoạt động bên dưới là yêu cầu cơ bản nếu bạn muốn ứng dụng chạy ổn định khi có tải.

Tiêu chí để chọn 7 điều này

Chúng tôi chọn từng điều dựa trên ba câu hỏi. Thứ nhất, nó có ảnh hưởng thực tế đến hiệu năng không. Thứ hai, lập trình viên có hay gặp và mất nhiều thời gian debug không. Thứ ba, bạn có thể áp dụng ngay mà không cần đổi kiến trúc lớn hay không.

Cần nói rõ: bài viết này không thay thế tài liệu chính thức. Hành vi cụ thể có thể khác giữa các phiên bản EF Core, nên bạn hãy kiểm chứng lại với phiên bản đang dùng trong dự án của mình.

DbContext là đơn vị làm việc, phải hiểu vòng đời của nó

DbContext giữ change tracker và kết nối tới cơ sở dữ liệu. Với web application, vòng đời thường nên bám theo request: tạo khi có yêu cầu đến, giải phóng khi trả kết quả về. Trong ASP.NET Core, bạn thường đăng ký DbContext với scope mặc định của dependency injection là đủ.

Lỗi hay gặp nhất là dùng chung một DbContext cho nhiều luồng chạy song song. Lỗi này hiếm khi lộ ra khi test trên máy cá nhân, vì lượng request thấp. Khi có tải thật, nó bắt đầu gây ra dữ liệu lạ hoặc exception khó tái hiện. Cấu hình sai vòng đời cũng có thể dẫn đến rò rỉ bộ nhớ, nên đừng xem nhẹ phần này.

Khi nào cần tracking, khi nào AsNoTracking là đủ

Tracking là cơ chế EF theo dõi các entity đã đọc để biết cần cập nhật gì khi gọi SaveChanges. Cơ chế này tiện, nhưng đổi lại là tốn bộ nhớ, nhất là khi bạn đọc một danh sách lớn.

Với truy vấn chỉ đọc, ví dụ trang danh sách sản phẩm trên một website bán hàng, bạn không cần tracking. Hãy dùng AsNoTracking. Ví dụ, một trang danh sách vài nghìn bản ghi mà không cần sửa gì sẽ nhẹ đi rõ rệt khi chuyển sang AsNoTracking. Chỉ bật tracking khi bạn thật sự định sửa entity rồi lưu lại. Đây là thay đổi nhỏ nhưng thường mang lại hiệu quả đáng kể.

Cái bẫy N+1 query và cách xử lý

N+1 xảy ra khi bạn lấy một danh sách, rồi trong vòng lặp lại truy cập quan hệ con của từng phần tử. Lấy 1 danh sách sinh ra 1 câu SQL, mỗi phần tử lại sinh thêm 1 câu, và tổng số câu nhân lên rất nhanh. Đây là lỗi chúng tôi gặp ở nhiều dự án khi rà soát mã.

Có hai cách xử lý thường dùng. Cách đầu tiên là Include để nạp kèm dữ liệu liên quan ngay trong cùng truy vấn. Cách thứ hai là Select ra DTO, chỉ lấy đúng các trường giao diện thực sự cần. Lưu ý rằng Include quá nhiều bảng có thể làm câu SQL nặng và chậm, nên hãy bật log SQL để đo thực tế thay vì đoán.

Migration: đổi schema mà vẫn giữ dữ liệu

Migration là cách EF ghi lại thay đổi cấu trúc cơ sở dữ liệu theo thời gian. Bạn tạo bằng lệnh dotnet ef migrations add, áp dụng bằng dotnet ef database update, rồi commit cùng mã nguồn để cả team đồng bộ.

Trước khi áp dụng lên môi trường thật, hãy đọc script SQL mà EF sinh ra. Một cái bẫy đáng nhớ: khi đổi tên cột, EF có thể hiểu thành xóa cột cũ rồi thêm cột mới, và dữ liệu cũ sẽ mất. Trong trường hợp này, hãy sửa script thủ công để dùng lệnh đổi tên. Và đừng bao giờ bỏ qua bước sao lưu dữ liệu trước khi chạy.

Lazy, eager hay explicit loading

Lazy loading tự động nạp quan hệ khi bạn truy cập thuộc tính, nghe rất tiện. Nhưng chính sự tiện đó khiến truy vấn ngầm phát sinh mà bạn không để ý. Trong hệ thống lớn, điều này rất khó kiểm soát.

Eager loading phù hợp khi bạn biết trước cần dữ liệu nào, thường đi kèm Include. Explicit loading hợp lý khi bạn chỉ cần nạp quan hệ trong một vài nhánh logic cụ thể. Theo kinh nghiệm của đội ngũ, bạn nên tắt lazy loading mặc định và chọn cách rõ ràng cho từng truy vấn.

EF Core, Dapper hay SQL thuần

Không có công cụ nào tốt nhất cho mọi tình huống. EF Core cho năng suất viết code cao và xử lý nghiệp vụ phức tạp khá tốt. Dapper cho phép bạn kiểm soát câu SQL chặt chẽ hơn, phù hợp với các truy vấn cần tối ưu. SQL thuần cho quyền kiểm soát cao nhất nhưng đòi hỏi nhiều công sức nhất.

Nhiều dự án chúng tôi từng làm dùng EF cho phần nghiệp vụ chính và Dapper cho các truy vấn báo cáo đặc thù. Điều quan trọng là bạn tự đo hiệu năng trên dữ liệu thật của mình trước khi quyết định. Nếu bạn muốn hiểu thêm về cách viết truy vấn gọn gàng ở phía C#, có thể đọc Top 7 kinh nghiệm thực tế về LINQ trong C# và .NET để nắm nền tảng trước khi so sánh các công cụ.

Những sai lầm hay gặp và cách kiểm tra trước khi lên production

Sai lầm đầu tiên là quên bật logging SQL, rồi không biết EF thật sự đang chạy câu lệnh gì. Hãy bật log ở môi trường dev và staging để nhìn tận mắt câu SQL sinh ra.

Sai lầm thứ hai là gọi SaveChanges bên trong vòng lặp. Mỗi lần gọi là một lượt giao dịch, nên rất chậm. Hãy gom thay đổi lại rồi gọi một lần. Cuối cùng, nên viết bộ test tích hợp với cơ sở dữ liệu thử nghiệm để các lỗi này lộ ra sớm. Nếu muốn tránh thêm các lỗi phổ biến khác, bạn có thể tham khảo Top 10 sai lầm thường gặp về Entity Framework.

Câu hỏi thường gặp

EF Core có chậm hơn SQL thuần không?

Có thể chậm hơn ở một số truy vấn phức tạp, nhưng với đa số nghiệp vụ thông thường khoảng cách không đáng kể. Quan trọng hơn là tránh các lỗi như N+1. Hãy đo trên dữ liệu thật để có câu trả lời chắc chắn cho dự án của bạn.

Nên dùng AsNoTracking cho mọi truy vấn không?

Không cần thiết. Với truy vấn chỉ đọc thì nên dùng. Nếu bạn định sửa entity rồi gọi SaveChanges, hãy giữ tracking bình thường.

Khi nào nên chuyển sang Dapper?

Khi bạn cần kiểm soát câu SQL chi tiết, hoặc có các truy vấn báo cáo nặng mà EF sinh ra chưa tối ưu. Nhiều dự án dùng EF cho nghiệp vụ chính và Dapper cho phần truy vấn đặc thù.

Migration có làm mất dữ liệu không?

Có thể, nếu bạn không đọc kỹ script. Đổi tên cột hoặc đổi kiểu dữ liệu là các trường hợp rủi ro. Luôn kiểm tra script SQL và sao lưu dữ liệu trước khi áp dụng.

EF Core có hỗ trợ đầy đủ mọi cơ sở dữ liệu không?

EF Core hỗ trợ nhiều hệ quản trị cơ sở dữ liệu thông qua provider riêng, nhưng mức độ hỗ trợ tính năng có thể khác nhau. Bạn nên kiểm tra tài liệu của provider đang dùng trước khi bắt đầu dự án.