Entity Framework Core: 2026 Performans Optimizasyonu ve En İyi Uygulamalar
EF Core 10 performans optimizasyonu: AsNoTracking, split query, toplu işlemler, LeftJoin ve named query filter kullanımı. Üretim ortamı için kapsamlı rehber.

Entity Framework Core 10, Kasım 2025'te .NET 10 LTS ile birlikte yayınlandı ve LeftJoin operatörleri, vektör arama, isimli sorgu filtreleri ve önemli SQL çeviri iyileştirmeleri sunuyor. Bu rehber, üretim ortamında sorgu hızı, bellek tahsisi ve ölçeklenebilirliği doğrudan etkileyen EF Core en iyi uygulamalarını ele alıyor.
EF Core 10, Kasım 2028'e kadar desteklenen Uzun Süreli Destek (LTS) sürümüdür. .NET 10 SDK ve çalışma zamanı gerektirir. Hala .NET 8 kullanan uygulamalar, geçiş tamamlanana kadar EF Core 8 (LTS) hedeflemelidir.
Sorgu İzleme: Ne Zaman Devre Dışı Bırakılmalı ve Neden
DbSet<T> üzerinden yapılan her çağrı, döndürülen varlıkları varsayılan olarak değişiklik izleyicisine ekler. İzleyici, orijinal özellik değerlerinin anlık görüntüsünü tutar, SaveChanges sırasında farkları hesaplar ve gezintiler arasındaki kimlik çakışmalarını çözer. Veriler doğrudan bir API yanıtına veya salt okunur bir görünüm modeline akıyorsa bu ek yük gereksizdir.
public async Task<List<ProductDto>> GetActiveByCategoryAsync(
int categoryId, CancellationToken ct)
{
return await _context.Products
.AsNoTracking() // skip change tracker entirely
.Where(p => p.CategoryId == categoryId && p.IsActive)
.OrderBy(p => p.Name)
.Select(p => new ProductDto // project to DTO at the database level
{
Id = p.Id,
Name = p.Name,
Price = p.Price
})
.ToListAsync(ct);
}AsNoTracking, varlık başına anlık görüntü oluşturma ve kimlik çözümleme ek yükünü ortadan kaldırır. Select ile birleştirildiğinde, yalnızca gerekli sütunlar ağ üzerinden iletilir. 50.000 satırlık bir tabloda bu desen, izlenen tam varlık sorgusuna kıyasla bellek tahsislerini tipik olarak %40-60 oranında azaltır.
Hiçbir zaman veri değiştirmeyen bağlamlar için varsayılanı kayıt sırasında ayarlamak mümkündür:
builder.Services.AddDbContext<CatalogContext>(options =>
options.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));Kartezyen Patlamasından Kaçınmak İçin Bölünmüş Sorgular
Include ile birden fazla koleksiyon gezintisine sahip bir varlığı yüklemek, JOIN'lerle tek bir SQL ifadesi oluşturur. İki veya daha fazla koleksiyon aynı anda yüklendiğinde, sonuç kümesi koleksiyonların Kartezyen çarpımı olarak büyür ve her kombinasyonda üst satır verilerini çoğaltır.
public async Task<Order?> GetWithDetailsAsync(int orderId, CancellationToken ct)
{
return await _context.Orders
.AsSplitQuery() // one SQL per Include
.Include(o => o.Items)
.ThenInclude(i => i.Product)
.Include(o => o.Payments)
.Include(o => o.ShippingEvents)
.FirstOrDefaultAsync(o => o.Id == orderId, ct);
}AsSplitQuery, yüklemeyi gezinti başına ayrı SQL ifadelerine böler. Dezavantajı: bir yerine birden fazla gidiş-dönüş, ancak her sonuç kümesi küçük kalır ve satır çoğaltma sorununu önler. EF Core 10 ayrıca bölünmüş sorgulardaki uzun süredir devam eden sıralama tutarsızlığını düzelterek alt sorgu sıralamasının birincil sorguyla eşleşmesini sağlar.
Tek bir koleksiyon gezintisi yüklenirken veya gidiş-dönüş gecikmesi yüksek olduğunda (bölgeler arası veritabanı çağrıları) tek sorgular tercih edilir. Belirli erişim deseni için karar vermeden önce her iki modu da karşılaştırmalı test etmek gerekir.
ExecuteUpdate ve ExecuteDelete ile Toplu İşlemler
Geleneksel EF iş akışları varlıkları yükler, özellikleri değiştirir, ardından SaveChanges çağrısı yapar. Binlerce satırı etkileyen toplu işlemler için bu, binlerce izlenen örnek ve bireysel UPDATE ifadeleri oluşturur. EF Core 7, işlemi tek bir SQL komutuna itmek için ExecuteUpdateAsync ve ExecuteDeleteAsync metodlarını tanıttı. EF Core 10, ifade ağacı yerine düz lambda kabul ederek API'yi daha da basitleştiriyor.
public async Task ApplySeasonalDiscountAsync(
int categoryId, decimal discountPercent, CancellationToken ct)
{
var affected = await _context.Products
.Where(p => p.CategoryId == categoryId && p.IsActive)
.ExecuteUpdateAsync(s =>
{
s.SetProperty(p => p.Price, p => p.Price * (1 - discountPercent / 100));
s.SetProperty(p => p.LastModified, DateTime.UtcNow);
}, ct);
// affected = number of rows updated
}Bu, tek bir UPDATE ... SET ... WHERE ifadesine çevrilir. Belleğe hiçbir varlık yüklenmez. 10.000 satırlık bir güncelleme için yürütme süresi saniyelerden (izlenen yaklaşım) milisaniyelere düşer.
Aynı desen silme işlemleri için de geçerlidir:
public async Task PurgeExpiredSessionsAsync(CancellationToken ct)
{
await _context.Sessions
.Where(s => s.ExpiresAt < DateTime.UtcNow)
.ExecuteDeleteAsync(ct);
}.NET mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
EF Core 10'da LeftJoin Operatörü
EF Core 10 öncesinde LEFT JOIN gerçekleştirmek, çoğu geliştiricinin her seferinde araştırması gereken belirli bir desende GroupJoin, SelectMany ve DefaultIfEmpty kombinasyonu gerektiriyordu. EF Core 10, LeftJoin'i birinci sınıf bir LINQ operatörü olarak ekliyor.
public async Task<List<EmployeeReportDto>> GetEmployeeDepartmentReportAsync(
CancellationToken ct)
{
return await _context.Employees
.LeftJoin(
_context.Departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new EmployeeReportDto
{
FullName = employee.FirstName + " " + employee.LastName,
Department = department.Name ?? "Unassigned",
HiredAt = employee.HiredAt
})
.OrderBy(r => r.FullName)
.ToListAsync(ct);
}Oluşturulan SQL standart bir LEFT JOIN yan tümcesi kullanır. RightJoin operatörü de mevcuttur. Her iki operatör de daha önce gerekli olan ayrıntılı üç metotlu zinciri ortadan kaldırır.
Çoklu Filtre Yönetimi İçin İsimli Sorgu Filtreleri
Genel sorgu filtreleri, EF Core 2.0'dan bu yana varlık türü başına tek bir filtreyle sınırlıydı. Hem yumuşak silme hem de çok kiracılık uygulayan uygulamalar, koşulları tek bir ifadede birleştirmek zorundaydı ve bunları seçici olarak devre dışı bırakamazdı. EF Core 10, isimli sorgu filtrelerini tanıtıyor.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>()
.HasQueryFilter("SoftDelete", i => !i.IsDeleted)
.HasQueryFilter("Tenant", i => i.TenantId == _tenantId);
}Yönetici uç noktaları, kiracı izolasyonunu etkin tutarken yumuşak silme filtresini devre dışı bırakabilir:
public async Task<List<Invoice>> GetAllIncludingDeletedAsync(CancellationToken ct)
{
return await _context.Invoices
.IgnoreQueryFilters(["SoftDelete"]) // tenant filter still applied
.ToListAsync(ct);
}Bu, filtre koşullarını manuel olarak ekleyen özel IQueryable uzantı metotlarına olan ihtiyacı ortadan kaldırır.
Bağlantı Dayanıklılığı ve Havuz Yapılandırması
Geçici veritabanı hataları (ağ kesintileri, Azure SQL yük devretmeleri, bağlantı havuzu tükenmesi) genellikle istek hattını çökerten istisnalara neden olur. EF Core yerleşik yeniden deneme mantığı sağlar, ancak varsayılanlar açık yapılandırma gerektirir.
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null); // retry on all transient errors
sqlOptions.CommandTimeout(30); // 30-second command timeout
}));Bağlantı dizesi havuz parametreleri de önemlidir:
Server=db.example.com;Database=AppDb;Min Pool Size=5;Max Pool Size=100;Connection Timeout=15;Min Pool Size, ani trafik artışları için hazır sıcak bağlantıları tutar. Max Pool Size, veritabanı aşırı yüklenmesini önlemek için toplam açık bağlantıları sınırlar. Varsayılan 100 değeri çoğu web uygulaması için uygundur, ancak yüksek verimli hizmetler gerçek eşzamanlı sorgu hacmine göre ayarlama gerektirebilir.
Otomatik yeniden denemeler, kullanıcı tarafından başlatılan işlemler içinde çalışmaz. Birden fazla işlem arasındaki geçici hataları ele almak için tüm işlemi context.Database.CreateExecutionStrategy().ExecuteAsync(...) kullanarak manuel bir yeniden deneme stratejisine sarın.
İndeksleme Stratejisi ve Sorgu Analizi
EF Core geçişleri indeksleri bildirimsel olarak tanımlayabilir, ancak hangi sütunların indeksleneceğini seçmek sorgu desenlerini anlamayı gerektirir.
modelBuilder.Entity<Order>(entity =>
{
// composite index for frequent query pattern
entity.HasIndex(o => new { o.CustomerId, o.Status, o.CreatedAt })
.HasDatabaseName("IX_Order_Customer_Status_Date");
// filtered index for active orders only
entity.HasIndex(o => o.Status)
.HasFilter("[Status] <> 'Cancelled'")
.HasDatabaseName("IX_Order_ActiveStatus");
});Filtrelenmiş indeksler, sorguların asla hedeflemediği satırları hariç tutarak indeks boyutunu azaltır. %80'i iptal edilmiş siparişleri olan bir tablo için, aktif durumlardaki filtrelenmiş bir indeks 5 kat daha küçük ve taranması daha hızlı olabilir.
Oluşturulan SQL'i analiz etmek için geliştirme ortamında günlük kaydını etkinleştirmek gerekir:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging());Günlüğe kaydedilen SQL'i SQL Server Management Studio'ya kopyalayıp mantıksal okumaları kontrol etmek için SET STATISTICS IO ON ile veya PostgreSQL'de EXPLAIN ANALYZE kullanarak çalıştırabilirsiniz. Sorgu planından gelen eksik indeks önerileri genellikle en yüksek etkili optimizasyon fırsatlarını ortaya çıkarır.
Yoğun Kullanılan Yollar İçin Derlenmiş Sorgular
LINQ ifade ağaçları her yürütmede SQL'e ayrıştırılır ve çevrilir. Dakikada binlerce kez çalışan sorgular için (örneğin kimlik doğrulama aramaları, oturum doğrulama), bu çeviri maliyeti birikir. Derlenmiş sorgular, çeviriyi uygulama başlangıcında önbelleğe alır.
public static class UserQueries
{
// compiled once, reused on every call
public static readonly Func<AppDbContext, string, CancellationToken, Task<UserSession?>>
GetActiveSession = EF.CompileAsyncQuery(
(AppDbContext ctx, string token, CancellationToken ct) =>
ctx.UserSessions
.AsNoTracking()
.FirstOrDefault(s => s.Token == token && s.ExpiresAt > DateTime.UtcNow));
}
// Usage in middleware
var session = await UserQueries.GetActiveSession(dbContext, bearerToken, ct);Derlenmiş sorgular, ifade ağacı ayrıştırma aşamasını tamamen atlar. Performans farkı yalnızca yüksek frekanslı yollarda (dakikada 1.000+ çağrı) ölçülebilir. Standart CRUD uç noktaları için, EF Core'daki yerleşik sorgu önbelleği zaten çeviri yeniden kullanımını yönetir.
EF Core 10'da Parametreli Koleksiyon Çevirisi
Bir ID listesine göre filtreleme yapan sorgular, veri erişimindeki en yaygın desenlerden birini temsil eder. EF Core 10, parametreli koleksiyonlar için varsayılan çeviri stratejisini değiştirir. Listeyi JSON dizisi olarak kodlamak (EF Core 8-9) veya sabitler olarak satır içi eklemek (EF Core 7 ve öncesi) yerine, EF Core 10 her değeri ayrı bir SQL parametresi olarak çevirir.
// Before (EF Core 8-9): JSON array parameter
// @__ids_0='[1,2,3]'
// SELECT ... WHERE Id IN (SELECT value FROM OPENJSON(@__ids_0))
// EF Core 10: individual parameters with padding
// SELECT ... WHERE Id IN (@ids1, @ids2, @ids3)
var orderIds = new[] { 101, 205, 389 };
var orders = await _context.Orders
.Where(o => orderIds.Contains(o.Id))
.ToListAsync(ct);Yeni yaklaşım, parametre doldurma yoluyla plan önbelleği şişmesinden kaçınırken veritabanı sorgu planlayıcısına kardinalite bilgisi verir. JSON yaklaşımının daha iyi performans gösterdiği durumlar için (çok büyük koleksiyonlar), davranışı sorgu başına geçersiz kılmak mümkündür:
var orders = await _context.Orders
.Where(o => EF.Parameter(orderIds).Contains(o.Id)) // force JSON mode
.ToListAsync(ct);Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Sonuç
- Değişiklik izleyici ek yükünü ortadan kaldırmak için her salt okunur sorguda
AsNoTracking()veSelectprojeksiyonları kullanılmalı - Kartezyen patlamasından kaçınmak için birden fazla koleksiyon gezintisi yüklerken
AsSplitQuery()uygulanmalı - Toplu işlemler için izlenen yükle-değiştir-kaydet desenlerini
ExecuteUpdateAsyncveExecuteDeleteAsyncile değiştirin - Ayrıntılı
GroupJoin/SelectMany/DefaultIfEmptyzincirini değiştirmek için EF Core 10'danLeftJoinoperatörünü benimseyin - Yumuşak silme ve çok kiracılığı bağımsız olarak yönetmek için isimli sorgu filtrelerini yapılandırın
- Üretim dayanıklılığı için
EnableRetryOnFailureayarlayın ve bağlantı havuzu boyutlarını ayarlayın - Tahminlere değil, gerçek sorgu desenlerine dayalı bileşik ve filtrelenmiş indeksler tanımlayın
- Derlenmiş sorguları dakikada 1.000'i aşan gerçekten yoğun kullanılan yollar için saklayın
- EF Core 10'un parametreli koleksiyon çevirisini varsayılan olarak yapmasına izin verin ve yalnızca karşılaştırmalı testler haklı çıkardığında geçersiz kılın
Daha fazla okuma: EF Core Advanced modülü bu desenleri mülakat bağlamında ele alır ve .NET ile Clean Architecture rehberi bu sorguları saran repository ve servis katmanlarının nasıl yapılandırılacağını gösterir.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

C# ve .NET Mülakat Soruları: 2026 Kapsamlı Rehber
En sık sorulan 17 C# ve .NET mülakat sorusu. LINQ, async/await, dependency injection, Entity Framework Core ve ileri düzey mimari kalıplar detaylı cevaplarla.

.NET 8: ASP.NET Core ile API Gelistirme
.NET 8 ve ASP.NET Core ile profesyonel bir REST API olusturmaya yonelik kapsamli rehber. Controller yapisi, Entity Framework Core, dogrulama ve en iyi uygulamalar.

Rust: 2026'da Deneyimli Geliştiriciler İçin Temel Kavramlar
Mevcut programlama bilgisiyle Rust'ı hızlıca öğrenmek mümkün. Sahiplik, ödünç alma, yaşam süreleri ve temel kalıplar; C++, Java veya Python'dan gelen geliştiriciler için açıklandı.