LINQ không có từ khóa NOT IN trực tiếp, nhưng đạt được cùng kết quả bằng Except, Where !Contains, hoặc Where !Any.
Cách 1: Except (Set Operation - Khuyến nghị cho collection)
var listA = Enumerable.Range(1, 6); // 1,2,3,4,5,6
var listB = new List { 3, 4 }; // 3,4
var result = listA.Except(listB); // 1,2,5,6
foreach (var item in result) Console.WriteLine(item);
- Loại bỏ duplicate trong listA.
- Sử dụng default equality comparer.
- Hiệu suất tốt (HashSet internal).
Cách 2: Where + !Contains (Linq to Objects)
var result = listA.Where(x => !listB.Contains(x)); // 1,2,5,6
// Tối ưu: Chuyển listB thành HashSet trước
var setB = new HashSet(listB);
var result = listA.Where(x => !setB.Contains(x));
Cách 3: Where + !Any (Linq to SQL / Entity Framework)
// Dành cho IQueryable (EF Core) - dịch sang SQL NOT IN / NOT EXISTS
var result = context.Products
.Where(p => !context.DiscontinuedProducts
.Select(dp => dp.ProductId)
.Contains(p.Id))
.ToList();
// Hoặc
var result = context.Products
.Where(p => !context.DiscontinuedProducts.Any(dp => dp.ProductId == p.Id))
.ToList();
Cách 4: Join + DefaultIfEmpty (Left Anti Join)
var result = from a in listA
join b in listB on a equals b into gj
from sub in gj.DefaultIfEmpty()
where sub == 0 // default(int) = 0
select a;
So sánh
| Phương pháp | Dùng cho | Duplicate | EF Core | Hiệu suất |
|---|---|---|---|---|
Except | In-memory collections | Loại bỏ | Không | O(n+m) |
Where !Contains | In-memory | Giữ | Không | O(n*m) -> dùng HashSet |
Where !Any | EF Core / IQueryable | Giữ | Có (NOT EXISTS) | Tùy query plan |
Left Anti Join | Complex join logic | Giữ | Có | Tương tự !Any |
Ví dụ với Object (Custom Equality)
var listA = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" } };
var listB = new[] { new { Id = 2, Name = "B" } };
// Except cần IEqualityComparer hoặc anonymous type cùng cấu trúc
var result = listA.Except(listB); // Works for anonymous type same structure
// Where !Any - flexible
var result2 = listA.Where(a => !listB.Any(b => b.Id == a.Id));
Lưu ý EF Core
Containstrên subquery lớn có thể gây performance issue -> dùngAnyhoặcJoin.Not Introng SQL có vấn đề với NULL values ->NOT EXISTS(từ!Any) an toàn hơn.