Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Khác biệt giữa IEnumerable và IQueryable trong C# (LINQ)

Bảng so sánh nhanh

Đặc điểmIEnumerable<T>IQueryable<T>
NamespaceSystem.CollectionsSystem.Linq
Kế thừa-Kế thừa IEnumerable<T>
Lazy LoadingKhông hỗ trợHỗ trợ (Deferred Execution)
Thực thi query DBLoad toàn bộ → lọc ở clientDịch sang SQL → lọc ở server
Extension MethodsNhận Func<T, bool> (delegate)Nhận Expression<Func<T, bool>> (expression tree)
Phù hợp choIn-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.