Trước EF Core 3.0, khi gặp biểu thức không dịch được sang SQL, EF tự động đánh giá phía client: kéo toàn bộ dữ liệu về rồi lọc bằng LINQ-to-Objects, chỉ log một warning.
Hành vi đó nguy hiểm vì im lặng. Một Where chứa method C# tự viết sẽ khiến EF SELECT cả bảng rồi mới lọc trong RAM — trên máy dev với vài chục dòng thì không thấy gì, lên production với hàng triệu dòng thì thành sự cố. Warning thì quá dễ bị bỏ qua.
Từ 3.0, EF chỉ cho phép client evaluation ở projection cấp cao nhất (lời Select cuối). Ở bất kỳ vị trí nào khác mà không dịch được thì ném InvalidOperationException.
// throws: FormatPhone is a C# method, EF cannot translate it
db.Users.Where(u => FormatPhone(u.Phone) == input).ToList();
// ok: top-level projection runs on the client after data is fetched
db.Users.Select(u => new { u.Id, Phone = FormatPhone(u.Phone) }).ToList();Hai cách xử lý:
1. Viết lại query để dịch được — dùng toán tử EF hiểu, hoặc EF.Functions.Like, hoặc mapping sang một hàm SQL đã đăng ký.
2. Chuyển sang client một cách tường minh bằng AsEnumerable() / ToListAsync() trước đoạn không dịch được — nhưng phải lọc bớt dữ liệu ở phía SQL trước, nếu không lại rơi đúng vào vấn đề cũ.
Điểm mấu chốt: đây là thiết kế "fail loud" — bắt lập trình viên quyết định ranh giới SQL/client thay vì để runtime âm thầm chọn phương án tệ.