İLERİ
Global Query Filters
Model düzeyinde tanımlanan, o entity'ye yapılan tüm sorgulara otomatik eklenen WHERE koşulları. Soft delete (IsDeleted), multi-tenancy (TenantId) gibi her sorguda tekrar tekrar yazılması gereken filtreleri tek noktada tanımlarsın.
Tanımlama
// Soft delete filtresi
builder.HasQueryFilter(p => !p.IsDeleted);
// Multi-tenant filtresi (DbContext'e inject edilen tenantId ile)
builder.HasQueryFilter(p => p.TenantId == _tenantId);
// Filtreli sorguyu devre dışı bırakma
var allProducts = context.Products.IgnoreQueryFilters().ToList();
Soft Delete filtresi nasıl çalışır:
// Normal sorgu
context.Products.Where(p => p.Price > 100).ToList();
-- EF'in ürettiği SQL (filtre otomatik eklenir):
SELECT [p].[Id], [p].[Name], [p].[Price]
FROM [Products] AS [p]
WHERE [p].[Price] > 100.0
AND [p].[IsDeleted] = CAST(0 AS bit); -- ← Otomatik eklendi!
-- EF'in ürettiği SQL (filtre otomatik eklenir):
SELECT p.id, p.name, p.price
FROM products AS p
WHERE p.price > 100.0
AND p.is_deleted = FALSE; -- ← Otomatik eklendi!
// Filtreyi devre dışı bırak (admin paneli, raporlama vb.)
context.Products.IgnoreQueryFilters().Where(p => p.Price > 100).ToList();
-- Filtresiz:
SELECT [p].[Id], [p].[Name], [p].[Price]
FROM [Products] AS [p]
WHERE [p].[Price] > 100.0; -- Filtre YOK
-- Filtresiz:
SELECT p.id, p.name, p.price
FROM products AS p
WHERE p.price > 100.0; -- Filtre YOK
Örnek veri — Products (IsDeleted ile):
| Id | Name | Price | IsDeleted | Silinen Ürünü Gören |
|---|---|---|---|---|
| 1 | Laptop | 84999.99 | 0 | Normal sorgu |
| 2 | Eski Model | 29999.00 | 1 | Sadece IgnoreQueryFilters() |
| 3 | iPhone | 54999.00 | 0 | Normal sorgu |
Birden Fazla Filter Kombinasyonu
// ⚠️ EF Core 9 ve öncesi: Tek bir entity'ye sadece BİR HasQueryFilter atanabilir.
// Birden fazla koşul AND ile birleştirilir:
builder.HasQueryFilter(p => !p.IsDeleted && p.TenantId == _tenantId);
// ❌ İkinci HasQueryFilter ilkini EZİP yazar (override) — EF9 ve öncesi:
builder.HasQueryFilter(p => !p.IsDeleted);
builder.HasQueryFilter(p => p.TenantId == _tenantId); // ← Sadece bu geçerli!
Named Query Filters — EF Core 10+ 🆕
EF Core 10 ile artık birden fazla isimli filter tanımlanabilir ve seçici olarak devre dışı bırakılabilir:
// 🆕 EF Core 10 (GA — .NET 10): İsimli query filter'lar
modelBuilder.Entity<Product>()
.HasQueryFilter("SoftDelete", p => !p.IsDeleted)
.HasQueryFilter("Tenant", p => p.TenantId == _tenantId);
// Sorguda sadece belirli filter'ları devre dışı bırakma:
var allProducts = await context.Products
.IgnoreQueryFilters(["SoftDelete"]) // Sadece soft-delete kaldırıldı, Tenant hâlâ aktif
.ToListAsync();
// Tüm filter'ları kaldırma (eski davranış):
var everything = await context.Products
.IgnoreQueryFilters() // Hepsi devre dışı
.ToListAsync();
-- IgnoreQueryFilters(["SoftDelete"]) ile:
SELECT * FROM [Products] WHERE [TenantId] = @tenantId; -- IsDeleted filtresi YOK
-- IgnoreQueryFilters() ile:
SELECT * FROM [Products]; -- Hiçbir filter yok
-- IgnoreQueryFilters(["SoftDelete"]) ile:
SELECT * FROM products WHERE tenant_id = @tenantId; -- is_deleted filtresi YOK
-- IgnoreQueryFilters() ile:
SELECT * FROM products; -- Hiçbir filter yok
Migration notu: EF Core 9'dan 10'a geçerken mevcut
HasQueryFilterçağrıların çalışmaya devam eder. İsimli filter'lar opsiyonel — yalnızca seçici disable ihtiyacın olunca kullan.
İlişkili Entity'lerde Filter Davranışı
// Category → HasQueryFilter(c => !c.IsDeleted)
// Product → HasQueryFilter(p => !p.IsDeleted)
// Include ile: her iki filter DA uygulanır
var categories = context.Categories
.Include(c => c.Products)
.ToList();
-- EF her iki tabloya da filter uygular:
SELECT [c].*, [p].*
FROM [Categories] AS [c]
LEFT JOIN [Products] AS [p] ON [p].[CategoryId] = [c].[Id] AND [p].[IsDeleted] = 0
WHERE [c].[IsDeleted] = 0;
-- EF her iki tabloya da filter uygular:
SELECT c.*, p.*
FROM categories AS c
LEFT JOIN products AS p ON p.category_id = c.id AND p.is_deleted = FALSE
WHERE c.is_deleted = FALSE;
Required navigation + filter = silent data loss!
EğerProduct.CategoryIdrequired ise ve Category soft-deleted ise → Product join'da kaybolur.
Çözüm: İlişkiyi optional yap veyaIgnoreQueryFilters()kullan.