Bảng so sánh nhanh
| Đặc điểm | IEnumerable<T> | IQueryable<T> |
|---|---|---|
| Namespace | System.Collections | System.Linq |
| Kế thừa | - | Kế thừa IEnumerable<T> |
| Lazy Loading | Không hỗ trợ | Hỗ trợ (Deferred Execution) |
| Thực thi query DB | Load toàn bộ → lọc ở client | Dịch sang SQL → lọc ở server |
| Extension Methods | Nhận Func<T, bool> (delegate) | Nhận Expression<Func<T, bool>> (expression tree) |
| Phù hợp cho | In-memory collections (List, Array) | Remote data sources (EF Core, LINQ to SQL) |
Cơ chế hoạt động
IEnumerable - Client-side filtering
// DbContext - Entity Framework
var context = new AppDbContext();
// IEnumerable: ToList() hoặc AsEnumerable() ép kiểu
IEnumerable<User> users = context.Users.Where(u => u.Name.StartsWith("A"));
// SQL thực tế chạy trên DB:
-- SELECT * FROM Users (KHÔNG CÓ WHERE!)
// Toàn bộ bảng Users được load vào RAM, sau đó .Where() lọc ở C#
IQueryable - Server-side filtering
// IQueryable: giữ nguyên expression tree
IQueryable<User> users = context.Users.Where(u => u.Name.StartsWith("A"));
// SQL thực tế chạy trên DB:
-- SELECT * FROM Users WHERE Name LIKE 'A%'
// Chỉ dữ liệu thỏa mãn điều kiện được truyền về client
Ví dụ so sánh SQL Generated
IEnumerable (Take sau Where)
var list = context.Users
.Where(u => u.Name.StartsWith("T"))
.AsEnumerable() // Ép sang IEnumerable
.Take(1); // Lọc ở client
-- SQL: SELECT [Id], [Name] FROM [Users] WHERE [Name] LIKE 'T%'
-- Load TẤT CẢ user tên T* về RAM, rồi Take(1) ở C#
IQueryable (Take trước Where)
var list = context.Users
.Where(u => u.Name.StartsWith("T"))
.Take(1); // Vẫn là IQueryable
-- SQL: SELECT TOP 1 [Id], [Name] FROM [Users] WHERE [Name] LIKE 'T%'
-- Chỉ 1 record được truyền về!
Khi nào dùng cái nào?
Dùng IEnumerable khi:
- Dữ liệu đã ở bộ nhớ (List<T>, Array, Collection đã load).
- Xử lý LINQ to Objects.
- Cần dùng method không hỗ trợ bởi IQueryable (custom logic phức tạp).
Dùng IQueryable khi:
- Entity Framework Core, LINQ to SQL, OData.
- Cần đẩy logic lọc/sắp xếp xuống database.
- Xử lý dữ liệu lớn, cần phân trang (Skip/Take).
- Xây dựng query động (dynamic query building).
Chuyển đổi giữa hai loại
// IQueryable → IEnumerable (force execute query)
var inMemory = queryable.AsEnumerable();
var inMemory = queryable.ToList(); // Execute ngay lập tức
// IEnumerable → IQueryable (chỉ wrapper, KHÔNG đẩy logic xuống DB)
var queryable = enumerable.AsQueryable();
// ⚠️ Cảnh báo: AsQueryable() trên List chỉ wrap, không biến List thành SQL provider!
Expression Tree vs Delegate
// IEnumerable: Func - compiled code, không thể phân tích
Func<User, bool> func = u => u.Age > 18;
// IQueryable: Expression - cây biểu thức, có thể phân tích/dịch sang SQL
Expression<Func<User, bool>> expr = u => u.Age > 18;
// EF Core đọc expr, dịch thành: WHERE Age > 18
Lưu ý hiệu năng quan trọng
⚠️ Cạm bẫy phổ biến: Gọi
.ToList() hoặc .AsEnumerable() quá sớm khiến toàn bộ dữ liệu được load về client, mất lợi thế của IQueryable. Luôn để query ở dạng IQueryable càng lâu càng tốt cho đến khi cần materialize.