Entity Framework eğitimi kapsamında ExecuteUpdateAsync kullanırken change tracker, optimistic concurrency, transaction retry ve SQL profilini doğru ele alın. Tek sorguda güncellemenin üretimdeki sınırlarını inceleyin.
Entity Framework Eğitimi: ExecuteUpdate ile Güvenli Toplu Güncelleme
Entity Framework Eğitimi: ExecuteUpdate change tracker'i neden atlar?
EF Core'da ExecuteUpdateAsync, sorguyu önce belleğe materialize etmeden doğrudan UPDATE üretir. Bu nedenle 50.000 satırlık bir durum geçişinde SELECT, entity oluşturma, snapshot alma ve SaveChanges change detection maliyetleri oluşmaz. Ancak DbContext'te aynı satırların zaten izlenen örnekleri varsa bunların alanları eski kalır. Bir .net core eğitimi içinde bu ayrım özellikle önemlidir: aynı request kapsamındaki tracked entity üzerinden karar vermek yerine güncelleme sonrasında context'i temizleyin veya yeni bir context açın.
var updated = await db.Orders
.Where(x => x.Status == OrderStatus.Pending
&& x.ExpiresAt < now)
.ExecuteUpdateAsync(setters => setters
.SetProperty(x => x.Status, OrderStatus.Expired)
.SetProperty(x => x.ExpiredAt, now), ct);
// Bu context daha once Order yuklediyse cache'teki entity eski olabilir.
db.ChangeTracker.Clear();
logger.LogInformation("Expired order count: {Updated}", updated);Bu çağrının dönüş değeri etkilenen satır sayısıdır; bu sayı iş kuralı için doğrudan kullanılabilir. Buna karşılık ExecuteUpdate, SaveChanges pipeline'ındaki entity state transition'larını, SaveChanges interceptor'larını ve entity bazlı audit kodunu çalıştırmaz. Audit zorunluysa güncelleme ifadesine audit kolonlarını ekleyin ya da aynı transaction içinde ayrı bir audit/outbox kaydı yazın. SQL'i çalıştırmadan filtreyi doğrulamak için sorgunun UPDATE'den önceki kısmında ToQueryString() kullanın; özellikle global query filter'ların tenant koşulunu içerdiğini burada kontrol edin.
Bu davranış hem csharp eğitimi hem de c# eğitimi içeriklerinde sık atlanan bir inceliktir: navigation property üzerinden atama yapılamaz, fakat filtrede ilişki kullanılabilir. Örneğin .Where(o => o.Customer.IsBlocked) sağlayıcıya göre EXISTS veya JOIN tabanlı bir alt sorgu üretirken, .SetProperty(o => o.Customer.Name, ...) geçerli değildir. Güncellenecek kolonlar hedef tablonun kolonları olmalıdır.
C# Eğitimi: Toplu güncellemede optimistic concurrency kurmak
SaveChanges, concurrency token içeren tracked bir entity güncellendiğinde token'ı WHERE koşuluna ekler ve 0 satır etkilenirse DbUpdateConcurrencyException fırlatır. ExecuteUpdateAsync ise bu kuralı otomatik kurmaz. SQL Server'daki rowversion, PostgreSQL'deki xmin veya uygulamanın yönettiği bir sürüm kolonu ile predicate'i açıkça yazmak gerekir. Aksi halde iki operatör aynı siparişi farklı duruma taşıyabilir ve son yazan öncekinin kararını sessizce ezer.
var version = Convert.FromBase64String(request.Version);
var affected = await db.Orders
.Where(o => o.Id == request.OrderId
&& o.Status == OrderStatus.Pending
&& o.Version == version)
.ExecuteUpdateAsync(s => s
.SetProperty(o => o.Status, OrderStatus.Approved)
.SetProperty(o => o.ApprovedAt, utcNow), ct);
if (affected != 1)
{
// 0 sonucu: kayit yok, tenant filtresi kaydi gizledi veya version degismis olabilir.
throw new ConcurrencyConflictException(request.OrderId);
}SQL Server rowversion kullanılıyorsa yeni token'ı SET etmeyin; veritabanı UPDATE sırasında üretir. İstemcinin sonraki komutu için yeni sürüm değerini dönmek gerekiyorsa, provider'ın RETURNING desteğine güvenen bir API varsaymayın: ExecuteUpdate sonuç olarak yalnızca satır sayısını verir. Güncellemeden sonra güvenli kapsamda minimal bir SELECT yapın veya response sözleşmesini yeni token gerektirmeyecek şekilde tasarlayın. Ayrıca global tenant filter 0 sonucunu 'bulunamadı' ile karıştırabilir; yetki sızıntısı yaratmamak için dışarıya tek bir conflict/not-found sözleşmesi vermek çoğu çok kiracılı sistemde daha güvenlidir.
Bir csharp kursu veya c# kursu laboratuvarında bu akışı iki paralel task ile test edin: ikisi de aynı Version değerini okusun, bir Barrier sonrasında ExecuteUpdateAsync çalıştırsın ve yalnızca birinin affected == 1 olduğunu doğrulayın. Bu test, in-memory provider yerine rowversion davranışını gerçekten uygulayan SQL Server veya PostgreSQL test konteyneri üzerinde anlamlıdır.
.NET Core Eğitimi: Transaction, outbox ve retry sınırı
Bir siparişi güncelleyip olay yayınlama niyetini outbox tablosuna yazıyorsanız iki SQL işlemi aynı veritabanı transaction'ında olmalıdır. Bağlantı kesintilerinde execution strategy callback'i yeniden çalıştırabileceğinden, outbox kaydının kimliği deterministik olmalı ve veritabanında unique index bulunmalıdır. Rastgele GUID'yi callback içinde üretmek retry sonrası iki farklı outbox olayı yaratabilir.
var eventId = DeterministicEventId.ForOrder(request.OrderId, request.Version);
var strategy = db.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using var tx = await db.Database.BeginTransactionAsync(ct);
var changed = await db.Orders
.Where(o => o.Id == request.OrderId && o.Version == version)
.ExecuteUpdateAsync(s => s
.SetProperty(o => o.Status, OrderStatus.Approved)
.SetProperty(o => o.ApprovedAt, utcNow), ct);
if (changed != 1)
throw new ConcurrencyConflictException(request.OrderId);
db.OutboxMessages.Add(new OutboxMessage(
eventId, "OrderApproved", request.OrderId.ToString(), utcNow));
await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
});SQL Server transient retry stratejisi veya Npgsql'nin benzer bağlantı hata senaryolarında, kullanıcı tarafından başlatılan transaction doğrudan retry edilemez. Transaction'ı CreateExecutionStrategy().ExecuteAsync callback'inin içine koymak, tüm birimi yeniden oynatılabilir hale getirir. Outbox için CREATE UNIQUE INDEX UX_OutboxMessages_Id ON OutboxMessages(Id) gibi bir benzersizlik kuralı ekleyin; aynı eventId ikinci kez yazılmak istenirse uygulamanın idempotent davranışını bu kısıt destekler.
.net core kursu müfredatında görülen yaygın hata, ExecuteUpdateAsync sonrasında HTTP isteği içinde doğrudan broker'a mesaj göndermektir. Veritabanı commit olurken broker çağrısı başarısız olursa kalıcı state değişmiş ama olay kaybolmuş olur. Outbox dispatcher'ı ayrı bir worker olarak, başarıyla yayınlanan kayıtları atomik biçimde işaretleyecek şekilde tasarlayın.
ASP.NET Core Eğitimi: Önce-sonra SQL ve CPU profilini ölçmek
ExecuteUpdate her iş yükünde daha hızlı değildir; dar bir filtre için indeks yoksa tek UPDATE yine milyonlarca satır tarar ve kilit süresini uzatır. Önce SaveChanges tabanlı akışı, sonra ExecuteUpdate akışını aynı veri dağılımı ve aynı bağlantı havuzu ısınmış durumdayken ölçün. Uygulama tarafında CPU örneklerini almak için test sırasında dotnet-trace collect -p $PID --profile cpu-sampling komutunu çalıştırın. Trace içinde ChangeDetector, snapshot karşılaştırması ve entity materialization frame'lerinin önceki akışta ne kadar zaman aldığını karşılaştırın.
// Once: tum satirlar cekilir, izlenir ve tek tek state degisir.
var orders = await db.Orders
.Where(o => o.Status == OrderStatus.Pending && o.ExpiresAt < now)
.ToListAsync(ct);
foreach (var order in orders)
order.Status = OrderStatus.Expired;
await db.SaveChangesAsync(ct);
// Sonra: tek parametrik UPDATE.
var count = await db.Orders
.Where(o => o.Status == OrderStatus.Pending && o.ExpiresAt < now)
.ExecuteUpdateAsync(s => s.SetProperty(o => o.Status, OrderStatus.Expired), ct);Veritabanı katmanında uygulama süre ölçümü tek başına yeterli değildir. SQL Server için test oturumunda SET STATISTICS IO, TIME ON; açın ve Query Store'dan iki planın logical read, CPU time ve duration değerlerini alın. PostgreSQL tarafında EXPLAIN (ANALYZE, BUFFERS) ile filtre planını, üretimde ise pg_stat_statements ile çağrı başına ortalama süreyi izleyin. (Status, ExpiresAt) bileşik indeksinin kolon sırası, WHERE koşulunun eşitlik ve aralık yapısına göre doğrulanmalıdır; yalnızca ExpiredAt indeksi bazı dağılımlarda çok fazla heap/page erişimi bırakabilir.
Profil sonucunu satır sayısı ile birlikte kaydedin: örneğin 100, 10.000 ve 1.000.000 aday satır için p50, p95, logical read ve lock wait değerlerini ayrı raporlayın. Büyük güncellemelerde 0.5-2 saniyelik tek transaction yerine anahtar aralıklarıyla batch kullanmak lock escalation riskini azaltabilir. Bu, asp.net core eğitimi uygulamalarında API timeout'unu çözmek için rastgele CommandTimeout yükseltmekten daha gözlemlenebilir bir yaklaşımdır.
Microsoft Teknolojileri Eğitimi: API ve Blazor istemcisi için sınırlar
HTTP endpoint'inde toplu güncelleme isteğini doğrudan UI'dan gelen filtre ifadesine çevirmeyin. İzin verilen state geçişlerini sabit bir komut modeliyle sınırlandırın, tenant'ı claim'den alın ve etkilenen satır sayısını response'a koyun. Aşağıdaki endpoint, DbContext'i request başına factory ile üretir; uzun ömürlü bir Blazor circuit'inde DbContext saklamaktan kaçınır.
app.MapPost("/orders/archive-expired", async (
IDbContextFactory<AppDbContext> factory,
ClaimsPrincipal user,
CancellationToken ct) =>
{
var tenantId = user.FindFirst("tenant_id")!.Value;
await using var db = await factory.CreateDbContextAsync(ct);
var now = DateTimeOffset.UtcNow;
var changed = await db.Orders
.Where(o => o.TenantId == tenantId
&& o.Status == OrderStatus.Expired
&& o.ArchivedAt == null)
.ExecuteUpdateAsync(s => s.SetProperty(o => o.ArchivedAt, now), ct);
return Results.Ok(new { archived = changed });
}).RequireAuthorization();Blazor eğitimi sırasında görülen kritik edge case şudur: kullanıcı iki sekmede aynı komutu başlatabilir ve UI'daki yerel liste güncel görünse bile sunucudaki predicate farklı sonuç verebilir. İstemciyi başarı varsayımıyla mutate etmek yerine response'taki archived sayısını kullanın, sonra ilgili sayfayı yeniden sorgulayın. Uzun yaşayan circuit için IDbContextFactory<TContext> kullanımı, aynı DbContext'in eşzamanlı iki event handler tarafından kullanılmasını da engeller.
Bu desen microsoft teknolojileri eğitimi içinde ASP.NET Core, EF Core ve Blazor'un sınırlarını birlikte gösterir: authorization endpoint'te uygulanır, tenant predicate SQL'e taşınır, UI yalnızca sonucu gösterir. IgnoreQueryFilters() ile bakım operasyonu yazmanız gerekiyorsa bunu normal kullanıcı endpoint'inden ayrı tutun ve tenantId koşulunu elle eklemeden hiçbir bulk komutu çalıştırmayın.
İlgili Eğitim
YTÜSEM İlgili Eğitim
.NET Core ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
entity framework eğitimi için ExecuteUpdateAsync mi SaveChanges mi seçilmeli?
Birçok satırda aynı kolonları değiştirecekseniz ve entity interceptor'larına ihtiyaç duymuyorsanız ExecuteUpdateAsync kullanın. Domain event, entity bazlı validasyon veya tracked graph değişikliği gerekiyorsa SaveChanges tercih edin. Kararı dotnet-trace CPU örnekleri ve SQL Server STATISTICS IO/TIME ya da EXPLAIN ANALYZE çıktısıyla, aynı veri setinde önce-sonra karşılaştırarak verin.
.net core eğitimi kapsamında ExecuteUpdate optimistic concurrency nasıl yapılır?
Concurrency token'ı WHERE koşuluna ekleyin ve dönüş değerinin tam olarak 1 olmasını bekleyin. SQL Server rowversion için örnek koşul Where(x => x.Id == id && x.Version == version) şeklindedir. Sonuç 0 ise kaydın silinmiş, filtreyle gizlenmiş veya başka bir işlem tarafından değiştirilmiş olabileceğini conflict olarak ele alın.
c# kursu projelerinde ExecuteUpdate sonrası DbContext neden eski veri döndürür?
ExecuteUpdate doğrudan SQL gönderir ve ChangeTracker içindeki entity snapshot'larını güncellemez. Aynı context daha önce ilgili entity'yi yüklediyse sonraki Find veya tracked LINQ sorgusu eski örneği döndürebilir. Komut sonrası db.ChangeTracker.Clear() çağırın veya okuma için yeni bir DbContext oluşturun.
blazor eğitimi uygulamasında bulk update için DbContext nasıl yönetilmeli?
Blazor circuit boyunca tek DbContext saklamayın. Her UI olayı için IDbContextFactory<AppDbContext> üzerinden context üretin, ExecuteUpdateAsync çağrısını await edin ve dispose edin. Böylece aynı context üzerinde eşzamanlı event handler kullanımı ve eski tracked entity sorunu oluşmaz.
AI / LLM Discovery
Bu makale Opendart Akademi Microsoft / C# eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


