İçeriğe geç

C# ile Dapper ve PostgreSQL: Veri Okuma İşlemlerinde Pratik İpuçları

Geçen hafta bir REST API projesinde çalışırken ilginç bir durumla karşılaştım. Dapper ile PostgreSQL’den veri çekerken, bazı sorguların beklenenden yavaş çalıştığını fark ettim. Normalde Dapper’ın hızlı olduğunu biliyordum ama bu sefer bir şeyler ters gidiyordu sanırım.

Dapper, .NET dünyasında mikro ORM olarak bilinen ve oldukça popüler bir kütüphane. Entity Framework kadar ağır değil, SQL kontrolünü tamamen size bırakıyor. Ben genelde REST API projelerimde Dapper kullanıyorum çünkü performansı iyi ve SQL yazmayı seviyorum zaten 🙂

Neden Dapper?

Entity Framework kullanmak yerine Dapper’ı tercih etmemin birkaç sebebi var aslında. İlk olarak SQL sorgularını kendiniz yazıyorsunuz, bu da tam kontrolü elinize veriyor. İkincisi performans gerçekten iyi, özellikle büyük veri setlerinde farkı hissediyorsunuz. Üçüncüsü ise basit, öğrenmesi kolay bir yapısı var.

Tabi her şeyin bir bedeli var değil mi? Dapper’da SQL yazmak zorundasınız, bu da bazen zahmetli olabiliyor. Ama bana göre bu bir avantaj, çünkü veritabanı seviyesinde ne yaptığınızı tam olarak biliyorsunuz.

PostgreSQL ile birlikte kullanınca da gayet uyumlu çalışıyor. Npgsql kütüphanesi sayesinde bağlantı kuruyorsunuz ve Dapper ile sorguları çalıştırıyorsunuz. Kurulumu da basit, NuGet’ten Dapper ve Npgsql paketlerini yüklüyorsunuz, hepsi bu kadar.

Basit Bir Sorgu Örneği

Şimdi basit bir örnek vereyim. Diyelim ki bir kullanıcı tablosundan veri çekmek istiyorsunuz. Önce bağlantı stringinizi appsettings.json dosyasına koyuyorsunuz, sonra Dapper ile sorguyu çalıştırıyorsunuz, en son da sonuçları alıyorsunuz. Bu kadar basit işte, 5 dakika sürer.

Kod şöyle bir şey oluyor genelde:

using (var connection = new NpgsqlConnection(connectionString))
{
var users = await connection.QueryAsync<User>(“SELECT * FROM users WHERE active = @Active”, new { Active = true });
return users.ToList();
}

Gördüğünüz gibi SQL sorgusunu direkt yazıyorsunuz ve parametreleri anonim obje olarak gönderiyorsunuz. Dapper otomatik olarak mapping yapıyor ve User sınıfınıza dönüştürüyor. Gayet pratik değil mi?

Performans Sorunları ve Çözümler

Neyse efendim, başta bahsettiğim performans sorununa geri dönelim. Sorguların yavaş çalışmasının sebebini araştırırken birkaç şey fark ettim. İlk olarak indexlerin eksik olduğunu gördüm, bu da sorguları yavaşlatıyordu. İkinci olarak bazı sorgularda gereksiz JOIN’ler vardı, bunları temizledim. Üçüncü olarak da connection pooling ayarlarını optimize ettim.

Bu arada aklıma geldi, geçen seferki sorun da böyleydi sanırım. Index unutmak klasik hata işte 🙂

PostgreSQL’de index oluşturmak için CREATE INDEX komutunu kullanıyorsunuz. Mesela users tablosunda email kolonuna index koymak isterseniz şöyle yapıyorsunuz:

CREATE INDEX idx_users_email ON users(email);

Index koyduktan sonra sorgu hızı ciddi şekilde arttı. Sanırım 3-4 saniye süren sorgu 200-300 milisaniyeye düştü, tam hatırlamıyorum ama çok fark vardı.

Connection Pooling Ayarları

Connection pooling da önemli bir konu. Npgsql varsayılan olarak pooling yapıyor ama bazen ayarları optimize etmek gerekiyor. Connection string’e Minimum Pool Size ve Maximum Pool Size parametrelerini ekleyebilirsiniz.

Mesela şöyle bir connection string kullanabilirsiniz:

“Host=localhost;Database=mydb;Username=postgres;Password=12345;Minimum Pool Size=5;Maximum Pool Size=20;”

Bu ayarlarla bağlantı havuzunda minimum 5, maksimum 20 bağlantı tutuyorsunuz. Böylece her seferinde yeni bağlantı açmak yerine havuzdan alıyorsunuz, bu da performansı artırıyor.

Tabi bu değerler projenize göre değişir. Küçük bir projede 5-20 yeterli ama büyük bir sistemde 50-100 arası bir şey kullanmanız gerekebilir. Ben genelde 10-30 arası kullanıyorum, iş görüyor.

Async/Await Kullanımı

Dapper ile async/await kullanmak da çok önemli. Özellikle REST API’lerde asenkron çalışmak performansı ciddi artırıyor. QueryAsync, ExecuteAsync gibi metodları kullanarak asenkron sorgular yapabiliyorsunuz.

Mesela bir kullanıcı eklemek için şöyle bir kod yazabilirsiniz:

public async Task<int> AddUserAsync(User user)
{
using (var connection = new NpgsqlConnection(connectionString))
{
var sql = “INSERT INTO users (name, email, active) VALUES (@Name, @Email, @Active) RETURNING id;”;
var id = await connection.ExecuteScalarAsync<int>(sql, user);
return id;
}
}

RETURNING id kısmı PostgreSQL’e özgü bir özellik bu arada. INSERT yaptıktan sonra oluşan id’yi direkt döndürüyor, çok pratik (ki bence bu çok önemli).

Async kullanırken dikkat etmeniz gereken bir nokta var. Connection’ı using bloğu içinde açıp kapatıyorsunuz, bu sayede bağlantı otomatik olarak kapanıyor. Unutursanız connection leak olur, sistem yavaşlar.

Çoklu Sonuç Setleri

Bazen bir sorguda birden fazla sonuç seti almak isteyebilirsiniz. Dapper’da QueryMultiple metodu ile bunu yapabiliyorsunuz. Mesela bir kullanıcının bilgilerini ve siparişlerini aynı anda çekmek isterseniz:

using (var connection = new NpgsqlConnection(connectionString))
{
var sql = “SELECT * FROM users WHERE id = @Id; SELECT * FROM orders WHERE user_id = @Id;”;
using (var multi = await connection.QueryMultipleAsync(sql, new { Id = userId }))
{
var user = await multi.ReadSingleAsync<User>();
var orders = await multi.ReadAsync<Order>();
return (user, orders.ToList());
}
}

İki sorguyu birleştirip tek seferde çalıştırıyorsunuz. Bu da veritabanına gidiş gelişi azaltıyor, performans artıyor. Gerçi her zaman gerekli değil ama bazen işe yarıyor.

Bursa’da bir kafede otururken bu kodu yazmıştım sanırım, kahve içerken 🙂 Neyse, konu dağıldı biraz.

Stored Procedure Kullanımı

PostgreSQL’de stored procedure kullanmak da mümkün. Dapper ile stored procedure çağırmak için CommandType parametresini StoredProcedure olarak ayarlıyorsunuz.

using (var connection = new NpgsqlConnection(connectionString))
{
var result = await connection.QueryAsync<User>(“get_active_users”, commandType: CommandType.StoredProcedure);
return result.ToList();
}

Stored procedure’ler karmaşık işlemler için kullanışlı. Mesela birden fazla tablo üzerinde işlem yapacaksanız, SQL kodunu stored procedure içine koyup tek seferde çağırabiliyorsunuz.

Ama ben pek kullanmıyorum açıkçası. SQL kodunu C# tarafında tutmayı tercih ediyorum, daha kolay yönetiliyor bana göre. Tabi bu kişisel tercih, herkes farklı düşünebilir.

Transaction Yönetimi

Transaction kullanmak da önemli bir konu. Özellikle birden fazla işlem yapacaksanız transaction açmanız gerekiyor. Dapper’da transaction kullanmak için IDbTransaction interface’ini kullanıyorsunuz.

using (var connection = new NpgsqlConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
try
{
await connection.ExecuteAsync(“INSERT INTO users …”, user, transaction);
await connection.ExecuteAsync(“INSERT INTO orders …”, order, transaction);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}

Transaction açtıktan sonra tüm işlemleri transaction parametresi ile yapıyorsunuz. Hata olursa Rollback çağırıyorsunuz, her şey geri alınıyor. Hata yoksa Commit ile onaylıyorsunuz.

Bu yapıyı kullanmayı unutursanız veri tutarsızlığı olabilir. Mesela kullanıcı eklendi ama sipariş eklenemedi, böyle durumlar oluşabilir. Transaction ile her şey ya tamamlanıyor ya da hiçbiri olmuyor, güvenli yani.

Sonuç ve Tavsiyeler

Dapper ile PostgreSQL kullanmak gerçekten pratik. Performansı iyi, kullanımı basit, SQL kontrolü sizde. REST API projelerinde rahatlıkla kullanabilirsiniz.

Birkaç tavsiyem var. İlk olarak indexleri unutmayın, performans için çok önemli. İkinci olarak async/await kullanın, özellikle API’lerde şart. Üçüncü olarak connection pooling ayarlarını optimize edin. Dördüncü olarak transaction kullanmayı unutmayın, veri tutarlılığı için gerekli.

Detaylı bilgi için Google’da “Dapper PostgreSQL best practices” ara, güzel kaynaklar çıkar. Ayrıca Dapper’ın GitHub sayfasında dokümantasyon var, oradan da bakabilirsiniz.

Neticede Dapper öğrenmesi kolay bir kütüphane. Birkaç gün pratik yaparsanız alışıyorsunuz. Ben de başlangıçta biraz zorlandım ama şimdi rahat kullanıyorum. Siz de deneyebilirsiniz, memnun kalırsınız sanırım 🙂