Dün gece yine kodlama moduna girdim biliyorsunuz, Bursa’da hava serinlemiş, pencereyi açtım biraz esinti alsın diye. Oturdum bilgisayar başına, bir REST API projesinde takıldım. Veri bütünlüğü önemli tabii, birden fazla işlem yapıyorsun ki ya hepsi geçsin ya da hiçbiri. İşte o an transaction’lara daldım, PostgreSQL ile C# entegrasyonunda Dapper’ı kullanıyorum ben hep, pratik geliyor. Aslında yıllardır böyle projeler geliştiriyorum, ama her seferinde ufak tefek detaylar çıkıyor ortaya.
Neyse efendim, transaction ne demek hatırlayalım mı? Basitçe, veritabanında bir dizi işlemi gruplayıp, hepsinin başarılı olmasını sağlıyorsun. Eğer bir yerde hata olursa, hepsini geri alıyorsun rollback diyorsun. PostgreSQL’de bu BEGIN, COMMIT, ROLLBACK komutlarıyla oluyor. C#’ta Dapper ile entegre etmek ise biraz kodlama gerektiriyor, ama korkulacak bir şey yok. Benim gibi günlük kodlama yapan biri için vazgeçilmez.
Bu arada aklıma geldi, geçen hafta bir projede transaction kullanmayı unutmuştum. İki tabloya veri yazıyordum, biri başarılı diğer hata verdi, veritabanı yarım kaldı. Neyse ki test ortamındaydı, production’a gitmeden fark ettim. O günden beri hep transaction koyuyorum, değil mi?
Evet, başlayalım mı konuya. Öncelikle NuGet’ten Dapper ve Npgsql paketlerini yüklemen lazım. Npgsql PostgreSQL için connector. Sonra connection string’ini ayarla, appsettings.json’da falan tutuyorum ben. Connection aç, transaction başlat. Dapper’da IDbTransaction interface’i var, onu kullanıyorsun. Mesela şöyle bir kod parçası:
using (var connection = new NpgsqlConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
try
{
// İşlemler buraya
connection.Execute(“INSERT INTO users (name) VALUES (@name)”, new { name = “Ali” }, transaction);
connection.Execute(“INSERT INTO orders (user_id, amount) VALUES (@user_id, @amount)”, new { user_id = 1, amount = 100 }, transaction);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
Bu kodla iki insert yapıyorsun, hata olursa rollback. Basit değil mi? Ama dikkat et, connection’ı dispose etmeyi unutma, using blokları kurtarıyor insanı. Ben bir keresinde connection’ı kapatmayı unutmuştum, memory leak oldu, program yavaşladı. Neyse efendim, o hatadan ders çıkardım.
Şimdi daha karmaşık bir senaryo düşünelim. Diyelim ki bir API endpoint’inde kullanıcı kaydı ve email gönderme var. Email başarısız olursa transaction rollback et, ama email veritabanı dışında. İşte orada distributed transaction’lar devreye giriyor, ama PostgreSQL için basit tutmak lazım. Ben genelde email’i transaction içinde değil, commit’ten sonra gönderiyorum. Ya da compensation pattern kullanıyorum, hata olursa temizle diyorsun.
Pratik Bir Örnek: REST API’de Kullanım
Benim projelerimde REST API geliştiriyorum C# ile .NET Core’da. Controller’da bir method yazıyorsun, service layer’a transaction’ı geçiriyorsun. Service’te Dapper ile çalış. Mesela UserService class’ında:
public bool CreateUserWithOrder(UserDto user, OrderDto order)
{
using var connection = _dbContext.CreateConnection();
connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var userId = connection.QuerySingle
order.UserId = userId;
connection.Execute(“INSERT INTO orders (user_id, amount) VALUES (@user_id, @amount)”, order, transaction);
transaction.Commit();
return true;
}
catch (Exception ex)
{
transaction.Rollback();
_logger.LogError(ex, “Transaction failed”);
return false;
}
}
Burada RETURNING ile insert’ten dönen ID’yi alıyoruz PostgreSQL özelliği. Çok pratik. Tabi dependency injection ile connection’ı inject et, hardcode etme. Benim deneyimime göre bu şekilde kod temiz kalıyor.
Fakat bazen transaction isolation level’ları karıştırıyor insan. PostgreSQL’de default Read Committed, ama Serializable istiyorsan ayarla. connection.BeginTransaction(IsolationLevel.Serializable). Deadlock riski artar ama veri tutarlılığı yüksek olur. Bir projede unuttum, concurrent kullanıcılar veri çakışması yaşadı. Sanırım 2-3 saat debug ettim, sonunda fark ettim.
Bu arada, Dapper’ın transaction desteği harika ama Entity Framework kullanıyorsan DbContext.Transaction var, daha declarative. Ben Dapper’ı seviyorum lightweight diye, ama ikisini karıştırma. Neyse efendim, PostgreSQL’de savepoint’ler de var, nested transaction gibi. transaction.Save(“point1”); sonra RollbackTo(“point1”); Hata olursa kısmi geri al.
Neden Transaction Önemli?
Düşünün ki banka uygulaması yazıyorsun, para transferi. Hesaptan çek, diğerine yatır. Ortada hata olursa para kaybolur. İşte transaction ACID property’lerini sağlar: Atomicity, Consistency, Isolation, Durability. PostgreSQL bu konuda çok güçlü, WAL logging ile durability sağlıyor. Bana göre her multi-statement operation’da kullanmak lazım.
Açıkçası ben kodlarken hep paranoidım, testlerde transaction’ları simüle ediyorum. Unit test’lerde in-memory db kullanıyorum, ama transaction test etmek zor. Integration test yazıyorum genelde. Gerçi zaman alıcı, ama production’da sorun çıkmasın diye değer.
Geçen gün şöyle bir fail yaşadım. Bir devre tasarımı projesinde gömülü sistem için veri logluyordum, ama transaction yoktu. Kart resetlendi, yarım veri kaldı. Kod tarafında da benzer, dikkat etmezsen veri tutarsızlığı olur. Neyse ki erken yakaladım, kısa sürdü düzeltmek.
Evet, şimdi alternatiflere bakalım. Stored procedure’lerde transaction yönetebilirsin PostgreSQL’de. C#’tan çağırıyorsun Dapper ile. connection.Execute(“CALL my_proc(@param)”, param, transaction); Ama prosedür içinde BEGIN…END; yaz. Karmaşık logic’ler için iyi, ama maintenance zorlaşıyor.
Sanırım en iyi yol hybrid. Basit işlemler Dapper transaction, karmaşık stored proc. Bu arada Microsoft Docs’ta Dapper örnekleri var, temel transaction’lar için bakabilirsin. Detaylı değil ama başlangıç için yeterli.
Ne güzel değil mi, kodlama dünyası böyle detaylarla dolu. Bazen saatlerce uğraşıyorsun bir satır için. Tabi ailemle vakit geçirirken kod düşünmüyorum, dağcılığa gidiyoruz Bursa civarında, kafayı dağıtıyorum. Ama dönünce hemen devam.
Olası Hatalar ve Çözümler
En sık hata connection timeout, transaction uzun sürerse. Timeout’ı ayarla Npgsql’de CommandTimeout ile. Başka deadlock, concurrent access’te. Index’leri optimize et tablolarda. Ben bir seferinde index unutmuştum, transaction 10 saniye sürdü, timeout yedi.
Fakat PostgreSQL cloud’da kullanıyorsan, AWS RDS gibi, connection pooling önemli. Npgsql otomatik pool yapıyor ama max pool size ayarla. Sanırım default 15, yoğun trafikte yetmeyebilir. Bir sitede gördüm, tam link hatırlamıyorum ama Google’da ‘npgsql connection pool’ yaz, çıkar.
Şimdi pratik bir ipucu: Async transaction’lar. C#’ta async/await ile yap. connection.ExecuteAsync(… , transaction); CommitAsync. API’lerde blocking olmaması için şart. Benim REST API’lerimde hep async kullanıyorum, performans artıyor.
Örnek async kod:
await using var connection = new NpgsqlConnection(connectionString);
await connection.OpenAsync();
await using var transaction = await connection.BeginTransactionAsync();
try
{
await connection.ExecuteAsync(“INSERT …”, param, transaction);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
Evet gayet güzel çalışıyor. Ama using’i await using yap, dispose async olsun. Detaylarını bilmiyorum ama Microsoft docs’ta var sanırım.
Neticede transaction yönetimi veri güvenliği için temel. Ben kodlarken hep ekliyorum, siz de unutmayın. Eğer yeni başlıyorsanız Google’da ‘c# dapper transaction postgresql example’ ara, bol örnek çıkar. Reddit’te de r/csharp subreddit’inde tartışılıyor, community faydalı.
Bir de şunu söyleyeyim, elektronik devre tasarlarken de benzer mantık var, sinyal zincirinde hata olursa resetle. Kodlama ile paralellik kuruyorum bazen. Neyse efendim, bu konu hakkında bu kadar. Deneyimlerinizi paylaşın yorumlarda, belki ben de öğrenirim bir şeyler.
Sonuç olarak, Dapper ile PostgreSQL transaction’ları pratik ve etkili. Küçük projelerden başla, alışınca vazgeçilmez olur. Benim gibi devam edin kodlamaya, keyfini çıkarın 🙂