# 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. - Published: 2026-04-15 - Updated: 2026-04-15 - Author: SharpSkill - Tags: entity-framework-core, dotnet, ef-core, performance, best-practices - Reading time: 12 min --- 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 yalnızca .NET 10 üzerinde çalışır** > > 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` ü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. ```csharp // ProductRepository.cs public async Task> 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: ```csharp // Program.cs builder.Services.AddDbContext(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. ```csharp // OrderRepository.cs public async Task 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 sorgu ne zaman kullanılmalı** > > 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. ```csharp // PromotionService.cs 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: ```csharp // CleanupService.cs public async Task PurgeExpiredSessionsAsync(CancellationToken ct) { await _context.Sessions .Where(s => s.ExpiresAt < DateTime.UtcNow) .ExecuteDeleteAsync(ct); } ``` ## 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. ```csharp // ReportService.cs public async Task> 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. ```csharp // AppDbContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .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: ```csharp // InvoiceRepository.cs public async Task> 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. ```csharp // Program.cs builder.Services.AddDbContext(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. > **Yeniden deneme mantığı ve işlemler** > > 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. ```csharp // AppDbContext.cs - OnModelCreating modelBuilder.Entity(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: ```csharp // Program.cs (development only) builder.Services.AddDbContext(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. ```csharp // CompiledQueries.cs public static class UserQueries { // compiled once, reused on every call public static readonly Func> 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. ```csharp // 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: ```csharp var orders = await _context.Orders .Where(o => EF.Parameter(orderIds).Contains(o.Id)) // force JSON mode .ToListAsync(ct); ``` ## Sonuç - Değişiklik izleyici ek yükünü ortadan kaldırmak için her salt okunur sorguda `AsNoTracking()` ve `Select` projeksiyonları 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 `ExecuteUpdateAsync` ve `ExecuteDeleteAsync` ile değiştirin - Ayrıntılı `GroupJoin`/`SelectMany`/`DefaultIfEmpty` zincirini değiştirmek için EF Core 10'dan `LeftJoin` operatö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 `EnableRetryOnFailure` ayarlayı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ü](/technologies/dotnet/interview-questions/ef-core-advanced) bu desenleri mülakat bağlamında ele alır ve [.NET ile Clean Architecture rehberi](/blog/dotnet/clean-architecture-dotnet-practical-guide) bu sorguları saran repository ve servis katmanlarının nasıl yapılandırılacağını gösterir. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/dotnet/ef-core-performance-best-practices