Entity Framework eğitimi kapsamında EF Core optimistic concurrency, transactional outbox ve ölçülebilir sorgu gözlemlenebilirliğini; üretimde hata ayıklanabilir C# örnekleriyle ele alın.
Entity Framework Eğitimi: EF Core Eşzamanlılık ve Outbox Deseni
Entity Framework eğitimi ile optimistic concurrency sınırını kurmak
.net core eğitimi veya .net core kursu içeriğinde çoğu zaman atlanan nokta şudur: HTTP isteğinin başında okunan bir satır, isteğin sonunda hâlâ aynı satır olmayabilir. EF Core bunu SQL Server'daki rowversion kolonu üzerinden UPDATE ... WHERE Id = @id AND Version = @version üretip etkilenen satır sayısını kontrol ederek yakalar. Sıfır satır etkilenirse EF Core DbUpdateConcurrencyException fırlatır; bu, uygulama seviyesinde sessiz son-yazanın-kazanması yerine bilinçli bir birleştirme kararı vermenizi sağlar.
public sealed class Subscription
{
public Guid Id { get; set; }
public int SeatCount { get; private set; }
[Timestamp]
public byte[] Version { get; private set; } = Array.Empty<byte>();
public void ChangeSeats(int value)
{
if (value is < 1 or > 500) throw new ArgumentOutOfRangeException(nameof(value));
SeatCount = value;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Subscription>()
.Property(x => x.Version)
.IsRowVersion();
}Version değerini istemciye base64 olarak taşıyın ve güncellemede orijinal değer olarak EF'ye verin; aksi halde tracked olmayan DTO akışında concurrency denetimi çalışmaz. Minimal API örneğinde context.Entry(entity).Property(x => x.Version).OriginalValue = Convert.FromBase64String(request.Version) ataması yapılmalıdır. İstemcinin gönderdiği yeni Version değerini doğrudan entity'ye yazmak yaygın bir hatadır: bu değer mevcut satırın sürümü değil, yalnızca karşılaştırılacak özgün sürümdür.
- SQL Server için migration üretin:
dotnet ef migrations add AddSubscriptionVersion. - Üretilen migration içinde kolonun
rowversionolduğuna bakın; saltvarbinary(8)tanımlamak veritabanının sürümü otomatik artırmasını sağlamaz. - PostgreSQL kullanılıyorsa SQL Server'a özgü
rowversionyerine uygulamanın artırdığılong Revisionalanını.IsConcurrencyToken()ile eşleyin.
C# eğitimi: Çakışmada database, original ve current değerlerini ayırmak
Bir csharp eğitimi veya csharp kursu örneğinde çakışmayı yalnızca 409 Conflict ile bitirmek yetersizdir. DbUpdateConcurrencyException.Entries içindeki CurrentValues kullanıcının denediği değişikliği, OriginalValues isteğin okuduğu sürümü, GetDatabaseValuesAsync() ise kalıcı güncel satırı verir. Alan bazlı merge yalnızca bağımsız alanlarda uygulanmalıdır; örneğin bakiye, stok ve koltuk sayısı gibi toplamsal invariant taşıyan alanlarda otomatik merge çift harcamaya yol açabilir.
try
{
await db.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException ex) when (ex.Entries.Single().Entity is Subscription local)
{
var entry = ex.Entries.Single();
var database = await entry.GetDatabaseValuesAsync(cancellationToken);
if (database is null)
return Results.NotFound(); // Satır başka bir istek tarafından silinmiş.
var serverSeats = database.GetValue<int>(nameof(Subscription.SeatCount));
var serverVersion = database.GetValue<byte[]>(nameof(Subscription.Version));
return Results.Conflict(new
{
attemptedSeatCount = local.SeatCount,
currentSeatCount = serverSeats,
version = Convert.ToBase64String(serverVersion)
});
}İstemciye dönen 409 gövdesine güncel sürümü koymak, kullanıcının formu yeni veriyle tekrar gönderebilmesini sağlar; ancak otomatik retry ile aynı SaveChangesAsync çağrısını döngüye almak doğru değildir. Retry öncesinde domain komutunu güncel state üzerinde yeniden doğrulayın. Örneğin ChangeSeats limiti tek başına yeterli değilse, aynı transaction içinde SELECT ... WITH (UPDLOCK, HOLDLOCK) veya veritabanı constraint'i ile toplam kota invariant'ını koruyun.
ASP.NET Core eğitimi: Transactional outbox ile olay kaybını önlemek
asp.net core eğitimi projelerinde sipariş kaydından sonra broker'a mesaj yayınlamak için önce SaveChangesAsync, sonra RabbitMQ veya Azure Service Bus çağırmak iki ayrı hata penceresi açar: veritabanı commit edildikten sonra süreç düşerse olay kaybolur; mesaj önce gider, commit başarısız olursa hayalet olay oluşur. Transactional outbox'ta domain değişikliği ve gönderilecek mesaj, aynı EF Core transaction'ında iki tabloya yazılır. Arka plan işçisi bu satırı daha sonra yayınlar.
public sealed class OutboxMessage
{
public Guid Id { get; init; } = Guid.NewGuid();
public DateTimeOffset OccurredOnUtc { get; init; }
public string Type { get; init; } = null!;
public string Payload { get; init; } = null!;
public DateTimeOffset? ProcessedOnUtc { get; set; }
public int Attempts { get; set; }
}
public sealed class OutboxInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData, InterceptionResult<int> result)
{
var context = eventData.Context!;
var events = context.ChangeTracker.Entries<IHasDomainEvents>()
.SelectMany(e => e.Entity.DequeueDomainEvents())
.ToList();
context.Set<OutboxMessage>().AddRange(events.Select(e => new OutboxMessage
{
OccurredOnUtc = DateTimeOffset.UtcNow,
Type = e.GetType().AssemblyQualifiedName!,
Payload = JsonSerializer.Serialize(e, e.GetType())
}));
return base.SavingChanges(eventData, result);
}
}
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseSqlServer(connectionString).AddInterceptors(new OutboxInterceptor()));Bu interceptor sadece EF Core'un yönettiği değişikliklerde işe yarar. Aynı iş akışında ExecuteUpdateAsync, Dapper veya doğrudan SQL kullanırsanız ChangeTracker domain event görmez; outbox satırını açıkça eklemeniz gerekir. Ayrıca yayıncı en az bir kez teslimat yapar: broker'a gönderimden sonra ProcessedOnUtc güncellemesi başarısız olabilir. Bu nedenle tüketici, OutboxMessage.Id değerini idempotency anahtarı olarak saklamalı ve aynı olay ikinci kez gelince yan etki üretmemelidir.
C# kursu için güvenli outbox tüketimi ve kilit stratejisi
Birden fazla BackgroundService pod'u aynı outbox kaydını seçerse çift yayın oluşur. PostgreSQL'de satırları atomik biçimde sahiplenmek için FOR UPDATE SKIP LOCKED kullanın; kilitli satır beklemek yerine diğer işçinin aldığı kaydı atlar. Aşağıdaki sorgu, sahiplenme işaretini ve seçilen mesajları tek transaction içinde döndürür; LIMIT değeri broker batch ve transaction süresine göre, örneğin 50 ile başlayarak ölçülmelidir.
WITH claimed AS (
SELECT id
FROM outbox_messages
WHERE processed_on_utc IS NULL
AND locked_until_utc IS NULL
ORDER BY occurred_on_utc
FOR UPDATE SKIP LOCKED
LIMIT 50
)
UPDATE outbox_messages o
SET locked_until_utc = now() + interval '2 minutes',
attempts = o.attempts + 1
FROM claimed
WHERE o.id = claimed.id
RETURNING o.id, o.type, o.payload;SQL Server'da aynı yaklaşım için UPDLOCK, READPAST, ROWLOCK ipuçları değerlendirilebilir; fakat READPAST davranışı veritabanının READ_COMMITTED_SNAPSHOT ayarıyla etkileşir. Üretim şemanızda IX_Outbox_Unprocessed (ProcessedOnUtc, LockedUntilUtc, OccurredOnUtc) gibi filtreyi destekleyen bir indeks olmadan işçi her turda tablo taraması yapar. Başarısız mesajlar için Attempts >= 10 olduğunda ayrı dead-letter tablosuna taşıma ve hata gövdesini kaydetme, zehirli mesajın sırayı sonsuza kadar bloke etmesini engeller.
Microsoft teknolojileri eğitimi: EF Core sorgularını izlemek ve test etmek
microsoft teknolojileri eğitimi kapsamında sorgu maliyetini tahminle değil telemetry ile inceleyin. EF Core'a Microsoft.EntityFrameworkCore.Database.Command log kategorisini yalnızca geliştirme veya örneklenmiş üretim trafiğinde açın; parametre değerlerini EnableSensitiveDataLogging() ile canlı ortamda açmayın, çünkü e-posta, token veya kişisel veri loglara girer. OpenTelemetry ile komut süresini trace üzerinde HTTP isteği ve outbox yayın span'ı ile ilişkilendirin.
builder.Services.AddOpenTelemetry()
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddSqlClientInstrumentation(o =>
{
o.RecordException = true;
o.SetDbStatementForText = true; // PII politikanız izin veriyorsa
})
.AddOtlpExporter());
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseSqlServer(connectionString)
.EnableDetailedErrors());Bir c# eğitimi uygulamasında önce p95 db.system=mssql span süresini ve çağrı başına SQL komut sayısını kaydedin; sonra değişikliği yapıp aynı yük profiliyle karşılaştırın. Örneğin liste ekranında Include(x => x.Items) yerine yalnızca gereken alanları Select ile projekte ettikten sonra, MiniProfiler veya OpenTelemetry trace'inde komut sayısının N+1 nedeniyle 101'den 1'e indiğini ve dönen kolonların azaldığını doğrulayın. AsNoTracking() ancak sonuçlar aynı DbContext içinde güncellenmeyecekse eklenmelidir; aksi halde attach sırasında yanlış state veya eksik original concurrency değeri üretebilir.
blazor eğitimi tarafında 409 yanıtını kullanıcıya 'kaydetme başarısız' olarak göstermek yerine, formun gönderdiği base64 version ile sunucunun döndürdüğü version'ı karşılaştırın ve alan bazlı fark ekranı üretin. Blazor Server'da uzun ömürlü circuit boyunca tek bir DbContext tutmayın; IDbContextFactory<AppDbContext> ile her komutta yeni context üretmek, eski tracked entity'lerin stale concurrency token taşımasını ve aynı context'in thread-safe olmama sorununu önler.
İ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 sırasında EF Core concurrency token neden güncellenmiyor?
SQL Server'da property'nin [Timestamp] veya Fluent API ile .IsRowVersion() olarak eşlendiğini ve migration'ın gerçek rowversion kolonu oluşturduğunu kontrol edin. PostgreSQL gibi sağlayıcılarda otomatik artırılan bir sürüm mekanizması yoksa Revision değerini uygulama kodunda artırıp .IsConcurrencyToken() kullanmanız gerekir.
ASP.NET Core eğitimi için outbox deseni RabbitMQ çift mesajlarını engeller mi?
Outbox, veritabanı değişikliği ile yayın niyetini aynı transaction'a alır; tek başına exactly-once teslimat vermez. Yayıncı, OutboxMessage.Id ile mesaj göndermeli; tüketici de bu kimliği benzersiz indeksli bir inbox veya işlenmiş-olay tablosunda saklayıp duplicate key durumunda yan etkiyi atlamalıdır.
Csharp kursu projesinde EF Core N+1 sorgusunu nasıl kanıtlarım?
MiniProfiler ile bir endpoint çağrısındaki SQL komutlarını sayın veya OpenTelemetry trace'lerinde db.statement span'larını inceleyin. Önce navigation erişimi yapan döngünün komut sayısını kaydedin, ardından Select projeksiyonu ya da uygun bir Include ekleyin ve aynı veri hacminde komut sayısı ile p95 veritabanı süresini tekrar ölçün.
Blazor eğitimi uygulamasında DbContext neden circuit boyunca saklanmamalı?
Blazor Server circuit'i birden fazla UI olayı boyunca yaşar; DbContext ise thread-safe değildir ve uzun süre tracked kalan entity'lerde eski concurrency token saklar. IDbContextFactory<TContext> enjekte edip her yükleme veya kaydetme operasyonunda await factory.CreateDbContextAsync() çağırın.
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.



