Bu asp.net core eğitimi yazısı, idempotency key, SQL Server benzersiz indeksleri ve transactional outbox ile aynı komutun eşzamanlı tekrarlarında doğru HTTP sonucu üretmeyi ele alır.
ASP.NET Core Eğitimi: Idempotent API Komutlarında Yarış Kontrolü
ASP.NET Core Eğitimi ile idempotency sözleşmesini sınırda kurmak
Ödeme, sipariş oluşturma veya abonelik değiştirme gibi yan etkili bir POST isteğinde istemci her yeniden denemede aynı Idempotency-Key değerini göndermelidir. Anahtarı kullanıcı veya tenant sınırına bağlamazsanız, iki farklı müşterinin aynı rastgele anahtarla birbirinin yanıtını geri alması mümkün olur. Header uzunluğunu 128 karakterle sınırlayın, boşlukları reddedin ve anahtarı bir tenant kimliğiyle birlikte veritabanına yazın.
public static class IdempotencyKeyPolicy
{
public static bool TryRead(HttpRequest request, out string key)
{
key = request.Headers["Idempotency-Key"].ToString().Trim();
return key.Length is >= 16 and <= 128
&& key.All(c => char.IsLetterOrDigit(c) || c is '-' or '_');
}
}
app.MapPost("/orders", async (
HttpRequest request,
CreateOrderRequest command,
OrderCommandService service,
CancellationToken cancellationToken) =>
{
if (!IdempotencyKeyPolicy.TryRead(request, out var key))
return Results.Problem(
statusCode: StatusCodes.Status400BadRequest,
title: "Geçersiz Idempotency-Key");
return await service.AcceptAsync(
tenantId: request.HttpContext.User.FindFirst("tenant_id")!.Value,
key,
command,
cancellationToken);
});Anahtar tek başına yeterli değildir. Aynı anahtarla farklı gövde gönderilirse ikinci isteği ilk isteğin sonucu gibi döndürmek, istemci hatasını gizler. Ham HTTP body baytlarının SHA-256 özetini kaydedin ve anahtar bulunduğunda özeti karşılaştırın. JsonSerializer.Serialize(command) ile özet üretmek yaygın ama sorunlu bir kestirmedir: alan sırası, varsayılan değerlerin yazımı ve serializer seçenekleri değişince aynı mantıksal komut farklı özet üretebilir. Sözleşmeniz bayt eşitliği istiyorsa gövdeyi middleware katmanında tamponlayın; mantıksal JSON eşitliği istiyorsa RFC 8785 uyumlu bir canonical JSON uygulamasını ayrıca seçip test edin.
Entity Framework Eğitimi: benzersiz indeks ve transactional outbox
Uygulama belleğindeki ConcurrentDictionary ile anahtar kilitlemek çoklu pod, uygulama yeniden başlatması ve rolling deployment altında işe yaramaz. Doğruluk sınırı, komutu kalıcı hale getiren veritabanıdır. SQL Server üzerinde TenantId + Key için benzersiz indeks, aynı anda gelen iki isteğin ikisinin de komut üretmesini engeller. Idempotency kaydı ve outbox mesajı aynı yerel transaction içinde yazılır; böylece HTTP 202 döndüğü halde worker'ın işleyebileceği mesajın hiç oluşmaması önlenir.
public sealed class IdempotencyRecord
{
public required string TenantId { get; init; }
public required string Key { get; init; }
public required byte[] RequestHash { get; init; }
public IdempotencyState State { get; set; }
public int? ResponseStatusCode { get; set; }
public string? ResponseBody { get; set; }
public DateTimeOffset CreatedUtc { get; init; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IdempotencyRecord>(b =>
{
b.HasKey(x => new { x.TenantId, x.Key });
b.Property(x => x.TenantId).HasMaxLength(64);
b.Property(x => x.Key).HasMaxLength(128);
b.Property(x => x.RequestHash).HasMaxLength(32).IsRequired();
b.Property(x => x.ResponseBody).HasColumnType("nvarchar(max)");
});
modelBuilder.Entity<OutboxMessage>(b =>
{
b.HasKey(x => x.Id);
b.HasIndex(x => new { x.State, x.VisibleAtUtc })
.IncludeProperties(x => new { x.Id, x.Payload });
});
}Komutu kabul eden transaction içinde önce IdempotencyRecord(State = Processing), sonra domain değişikliği veya iş emri, en son OutboxMessage eklenmelidir. SaveChangesAsync çağrılarını iki ayrı transaction'a bölmekten kaçının. İlk kayıt başarılı, outbox başarısız olursa tekrar istek 202 veya tamamlanmış gibi görünür fakat çalıştırılacak iş yoktur. SQL Server'da benzersiz indeks ihlali için SqlException.Number değerleri 2601 ve 2627'dir; DbUpdateException içinden bu değerleri ayırın. İhlal aldığınız DbContext ile devam etmek yerine transaction'ı rollback edin ve yeni bir DbContext ile mevcut kaydı okuyun.
public async Task<IResult> AcceptAsync(
string tenantId, string key, CreateOrderRequest command, CancellationToken ct)
{
var hash = RequestHash.FromRawBody(command.RawBody);
await using var tx = await _db.Database.BeginTransactionAsync(ct);
try
{
_db.IdempotencyRecords.Add(new IdempotencyRecord
{
TenantId = tenantId,
Key = key,
RequestHash = hash,
State = IdempotencyState.Processing,
CreatedUtc = _clock.GetUtcNow()
});
_db.OutboxMessages.Add(OutboxMessage.ForOrder(tenantId, key, command));
await _db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
return Results.Accepted($"/operations/{tenantId}/{key}");
}
catch (DbUpdateException ex) when (SqlServerErrors.IsUniqueKey(ex))
{
await tx.RollbackAsync(CancellationToken.None);
return await _replay.ReadAsync(tenantId, key, hash, ct);
}
}C# Eğitimi için tekrar istek durumları ve dış sistem yan etkileri
Tekrar istekte üç açık sonuç üretin: istek özeti farklıysa 422, kayıt Processing durumundaysa 202 ve operation URL'si, kayıt Completed durumundaysa saklanan HTTP durum kodu ile saklanan response body. Bu ayrım istemcinin zaman aşımını gerçek bir hata sanıp yeni anahtarla ikinci sipariş oluşturmasını engeller. Failed durumunu otomatik olarak yeniden çalıştırılabilir kabul etmeyin; hata sınıfı, retry sayısı ve dış sağlayıcının sonucu kayda alınmadan tekrar çalıştırmak çift tahsilat doğurabilir.
public IResult ToReplayResult(IdempotencyRecord record, byte[] incomingHash)
{
if (!CryptographicOperations.FixedTimeEquals(record.RequestHash, incomingHash))
return Results.Problem(
statusCode: StatusCodes.Status422UnprocessableEntity,
title: "Idempotency-Key farklı bir istek gövdesiyle kullanıldı");
return record.State switch
{
IdempotencyState.Processing =>
Results.Accepted($"/operations/{record.TenantId}/{record.Key}"),
IdempotencyState.Completed =>
Results.Content(record.ResponseBody!, "application/json", record.ResponseStatusCode),
_ => Results.Problem(statusCode: 409, title: "İşlem yeniden değerlendirme bekliyor")
};
}Worker bir ödeme sağlayıcısına veya e-posta servisine çağrı yapıyorsa veritabanı transaction'ını ağ çağrısı boyunca açık tutmayın. SQL kilit süresi uzar ve bağlantı havuzu baskı altında kalır; ayrıca transaction rollback olsa bile uzak sistemdeki istek geri alınmaz. Bunun yerine outbox mesajını kiralayın ve dış sağlayıcıya aynı Idempotency-Key değerini iletin. Worker, sağlayıcı başarılı yanıt verdikten hemen sonra ölürse lease süresi bitince başka worker mesajı tekrar alabilir. Sağlayıcının da aynı anahtarı deduplicate etmesi bu pencereyi güvenli kılar. Bu nedenle transactional outbox 'exactly once' değil, yerel kayıtta atomiklik ve dış çağrıda idempotent tekrar denemesidir.
.NET Core Eğitimi: outbox leasing, kilit analizi ve yük testi
Birden fazla worker'ın aynı outbox satırını seçmesini önlemek için SQL Server'da satırı seçme ve lease atama işlemini tek statement yapın. UPDLOCK seçilen satır için update lock alır, READPAST başka worker'ın kilitlediği satırı atlar, LeaseUntilUtc ise worker çökmesi sonrası görünürlük sağlar. Lease süresi dış servis timeout'ından kısa seçilirse aynı mesaj eşzamanlı iki kez işlenebilir; örneğin HTTP timeout 20 saniyeyse 30 saniyelik lease ancak sağlayıcı çağrısının kesin 20 saniye altında kaldığı bir politikayla anlamlıdır.
;WITH candidate AS
(
SELECT TOP (1) *
FROM dbo.OutboxMessages WITH (UPDLOCK, READPAST, ROWLOCK)
WHERE State = 0
AND VisibleAtUtc <= SYSUTCDATETIME()
ORDER BY VisibleAtUtc, Id
)
UPDATE candidate
SET State = 1,
LeaseUntilUtc = DATEADD(second, 30, SYSUTCDATETIME())
OUTPUT INSERTED.Id, INSERTED.Payload, INSERTED.IdempotencyKey;Bu akışta performans değişikliği ancak ölçümle değerlendirilir. Önce IX_OutboxMessages_State_VisibleAtUtc indeksi olmadan ve sonra EF Core'daki bileşik indeks ile aynı staging verisinde SQL Server SET STATISTICS IO, TIME ON çalıştırın. Karşılaştırmada lease sorgusunun logical reads, CPU time ve elapsed time değerlerini kaydedin. Eşzamanlı API davranışı için k6 ile aynı anahtarı paralel gönderin; başarı ölçütü 100 isteğin en fazla bir outbox kaydı oluşturması, diğerlerinin yalnızca 202 veya önceden kaydedilmiş yanıt almasıdır.
import http from 'k6/http';
import { check } from 'k6';
export const options = { vus: 50, iterations: 100 };
const key = 'loadtest-order-7f3f57b1-0001';
export default function () {
const response = http.post(
'https://staging.example.com/orders',
JSON.stringify({ sku: 'BK-42', quantity: 1 }),
{ headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key } }
);
check(response, {
'only accepted or replay response': r => r.status === 202 || r.status === 200
});
}Uygulama tarafındaki maliyeti görmek için yük testi sırasında dotnet-counters monitor -p PID System.Runtime Microsoft.AspNetCore.Hosting ile allocation rate, GC heap size ve request queue değerlerini izleyin; CPU hot path bulmak için ayrıca dotnet-trace collect -p PID --profile cpu-sampling alın. Önce body tamponlama kapalı, sonra ham body hashleme açık durumda aynı k6 senaryosunu çalıştırın. Böylece idempotency için eklenen bellek tahsisini p95 gecikme, allocation rate ve SQL logical read ile birlikte raporlayabilirsiniz.
Microsoft teknolojileri eğitimi yolunda uygulanabilir çalışma planı
Bir .net core kursu veya csharp kursu kapsamında bu konuyu öğrenirken küçük bir API yazıp SQL Server container'ını yerelde ayağa kaldırın: docker run --name idem-sql -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD=StrongPassw0rd! -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest. Ardından aynı key ile 50 paralel POST gönderen entegrasyon testi yazın ve veritabanında SELECT COUNT(*) FROM OutboxMessages WHERE IdempotencyKey = @key sonucunun 1 olduğunu doğrulayın.
.net core eğitimi, csharp eğitimi ve c# eğitimi içeriklerinde yalnızca controller yazmak yerine bu testi CI'a eklemek daha değerlidir. Benzer biçimde c# kursu ve entity framework eğitimi çalışmalarında 2601/2627 hata ayrımını gerçek SQL Server ile doğrulayın; EF Core InMemory provider benzersiz indeks ve transaction davranışını taklit etmez. asp.net core eğitimi içinde operation polling endpoint'i ekleyin; blazor eğitimi pratiğinde de istemciyi 202 yanıtında operation URL'sini poll edecek şekilde kurun. Bu akış, microsoft teknolojileri eğitimi kapsamındaki HTTP sözleşmesi, EF Core modelleme ve SQL kilit davranışını aynı senaryoda birleştirir.
İlgili Eğitim
YTÜSEM İlgili Eğitim
.NET Core ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
.net core kursu projelerinde Idempotency-Key için Redis kilidi yeterli mi?
Hayır. Redis kilidi ağ bölünmesi, TTL bitimi ve uygulama yeniden başlatması altında kalıcı komut kaydının yerini tutmaz. Redis'i kısa süreli response cache veya rate limit için kullanabilirsiniz; komutun tekilliğini SQL Server benzersiz indeksiyle, işin yayınlanmasını aynı transaction içindeki outbox satırıyla doğrulayın.
entity framework eğitimi sırasında DbUpdateException sonrası aynı DbContext ile sorgu yapılır mı?
Özellikle PostgreSQL gibi transaction hata sonrası abort durumuna geçen sağlayıcılarda yapılmamalıdır. SQL Server'da da change tracker başarısız eklenen entity'leri taşımaya devam eder. Transaction'ı rollback edin, başarısız DbContext'i bırakın ve yeni scope veya IDbContextFactory ile TenantId ve Key üzerinden kaydı tekrar okuyun.
csharp eğitimi için idempotent endpoint 200 mü yoksa 202 mi dönmeli?
İş aynı HTTP isteğinde tamamen bitiyor ve response güvenle kaydedilebiliyorsa ilk çağrı ile tekrar çağrısı aynı 200 veya 201 response'unu dönebilir. Outbox worker ile asenkron işliyorsanız 202 Accepted ve operation URL'si daha doğru sözleşmedir. Tekrar çağrısında Processing için yine 202, Completed için kaydedilmiş nihai durum kodunu döndürün.
blazor eğitimi projesinde 202 Accepted yanıtı nasıl izlenmeli?
Blazor istemcisi POST sonrası Location veya response içindeki operation URL'sini almalı, sabit 100 ms polling yerine Retry-After header'ını veya üstel geri çekilmeyi kullanmalıdır. Poll isteği 200 Completed dönene kadar devam eder; kullanıcı sayfayı yenilese bile operation kimliği route veya local storage üzerinden korunabilir.
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.


