← Tüm yazılar

Birden çok kaydı TransactionScope ile atomik kaydetmek

Ana kayıt oluşup ona bağlı kayıt oluşmadığında kullanıcı yarım bir işle karşılaşır. İki INSERT aynı iş kararının parçalarıysa transaction sınırı da onları birlikte kapsamalı.

t.Turgay
Birden çok kaydı TransactionScope ile atomik kaydetmek

Ana kayıt var, alt kayıt yok; ekranda ise işlem başarılı görünüyor. Bir başvuru veya onay akışında iki INSERT'in ayrı ayrı doğru olması, işin tamamlandığı anlamına gelmez. Arşivimdeki servislerde toplu yükleme, onay ve kapasite kontrolü aynı iş akışına giriyor. TransactionScope'u bu bağlamda, birden fazla yazmanın hangi iş kararıyla birlikte sonuçlanacağını ifade etmek için ele alıyorum.

Bir işlemin atomik sınırını çizmek

Örnekte ParentDemo tablosuna ana kayıt, ChildDemo tablosuna bağlı kayıt yazılıyor. İkisi tek iş: ya birlikte kalacaklar ya ikisi de geri alınacak. Gerçek uygulamada transaction sınırını repository'nin her metoduna ayrı ayrı koymak bu bütünlüğü sağlamaz; iki çağrıyı birlikte yöneten servis hangi adımların aynı sonuca ait olduğunu bilmelidir.

Kod Microsoft.Data.SqlClient ve System.Transactions ile bir çağıran metot içine yerleşir. Deneme veritabanında ParentDemo.Id ve ona başvuran ChildDemo.ParentId kolonları hazırlanmalı. Id=1 bu küçük gösterim için sabit; gerçek kimlik üretimi örneğin kapsamı dışında.

using System.Transactions;
using Microsoft.Data.SqlClient;
static async Task SaveAsync(string connectionString, CancellationToken token)
{
    using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
    await using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync(token);
    await using var command = connection.CreateCommand();
    command.CommandText = "INSERT INTO dbo.ParentDemo(Id) VALUES(1)";
    await command.ExecuteNonQueryAsync(token);
    command.CommandText = "INSERT INTO dbo.ChildDemo(ParentId) VALUES(1)";
    await command.ExecuteNonQueryAsync(token);
    scope.Complete();
}

AsyncFlowOption neden özellikle yazılıyor?

TransactionScope, ortamdaki transaction bağlamını kullanır. await sonrasında yürütme farklı bir devam noktasına taşınabilir. TransactionScopeAsyncFlowOption.Enabled, bu bağlamın async devamlar boyunca akmasını sağlar. Yalnız metodun async olması transaction bağlamını otomatik olarak bu niyetle yapılandırmaz. Kodda görünen bu seçenek, arada await olan iş akışının önemli bir parçasıdır.

Bağlantı scope içinde açılır ve sağlayıcının enlistment davranışıyla o bağlama katılır. Her iki komut aynı bağlantıdan üretildiği için burada iki ayrı kaynak oluşturmuyoruz. İptal token'ı OpenAsync ve iki ExecuteNonQueryAsync çağrısına iletiliyor; istemcinin vazgeçmesi halinde alt I/O çağrılarının da bundan haberdar olması gerekir.

Complete bir başarı oyudur

scope.Complete çağrısı, bu kapsamın işlemi başarılı kabul ettiğini bildirir; tek başına “şu anda her şey commit oldu” garantisi değildir. Kapsam kapanırken commit süreci tamamlanır. İkinci komut hata verdiğinde Complete'e ulaşılmaz ve kapsamın kapanışı işlemi geri alma yönüne gider. Bu yüzden kullanıcıya başarıyı yalnız Complete satırına bakıp erken göndermem; using kapsamı da hatasız bitmiş olmalı.

İç içe scope kullanıldığında varsayılan Required mevcut transaction'a katılabilir. İçteki Complete, dış kapsamın daha sonra rollback yapmasını engellemez. RequiresNew ise bağımsız bir transaction anlamına gelebilir; dış iş başarısız olduğunda içerideki kaydın kalması isteniyorsa bu karar açıkça gerekçelendirilmelidir.

Atomiklik, eşzamanlılık kuralını tek başına seçmez

Örnekte izolasyon ve timeout açıkça ayarlanmadığı için TransactionScope varsayılanları geçerlidir. Gerçek işte bunları veri yarışı ve bekleme bütçesiyle birlikte seçerim. “Kontenjan var mı?” sorgusunu transaction dışına koyup sonra yazmak iki isteğin aynı boşluğu görmesine izin verebilir. Kontrol ve yazma aynı tutarlılık sınırında tasarlanmalı; benzersizlik gibi kurallar veritabanında da korunmalıdır.

Birden çok bağlantı veya kaynak eklemek distributed transaction gereksinimine ve platforma bağlı kısıtlara yol açabilir. HTTP çağrısı veya e-posta gönderimi scope açıldı diye rollback edilebilir hale gelmez. Bir bildirimin ancak kayıt sonrası gönderilmesi gerekiyorsa commit sonrası işleme veya outbox gibi ayrı bir tasarıma ihtiyaç vardır.

Yarım kaydı bilinçli olarak üretmeye çalışmak

Önce iki yazmanın başarılı olduğu durumda iki tabloyu kontrol ederim. Sonra alt kaydı foreign key veya kontrollü bir hata ile başarısız kılar, ana kaydın da kalmadığını doğrularım. İptal, timeout ve tekrarlanan Id=1 denemeleri ayrı sonuçlar üretir. Bu sabit kimlikle örneği ikinci kez çalıştırmak bir yeniden deneme stratejisi değildir; önceki veriyi ve hedef kısıtlarını hesaba katmak gerekir.

TransactionScope çalışma modeli; Async transaction bağlamı.

✳ Bu yörüngenin sonuna geldin.
Diğer yazıları keşfet →
turgay.Biraz kod, biraz piksel, bolca merak.© 2026 · Dünya’dan sevgilerle ✳