DapperExample · Dapper ile Katmanlı Ürün Yönetimi API Örneği

Parametreli SQL, OUTPUT INSERTED.Id ve asenkron Dapper çağrılarını bağlantı fabrikası, repository ve servis katmanlarıyla birleştiren ürün CRUD API’si.

Bir yönetim ekranında yeni ürün kaydettiğimizi düşünelim. Arayüz, kayıt tamamlandığında ürünün detay adresine geçmek için SQL Server’ın ürettiği kimliği bilmek ister. DapperExample bu ihtiyacı, SQL sorgusundan HTTP yanıtına kadar izlenebilen küçük bir .NET 8 Web API örneğiyle ele alıyor. Ürün modelinde kimlik, ad, fiyat ve oluşturulma tarihi var. Ekleme akışının yanında listeleme, kimliğe göre getirme, güncelleme ve silme de aynı model üzerinden gösteriliyor.

SQL sorgusu yeni kimliği API’ye nasıl döndürür?

POST /api/Product isteğinde ProductController, ürünü IProductService üzerinden uygulama katmanına iletir. ProductService, CreatedDate alanını DateTime.Now ile doldurur ve ürünü repository’ye aktarır. ProductRepository içindeki INSERT sorgusu [dbo].[Product] tablosuna ad, fiyat ve oluşturulma tarihini yazar. Sorgudaki OUTPUT INSERTED.[Id] ifadesi üretilen kimliği aynı işlemden döndürür. Dapper bu değeri ExecuteScalarAsync<int> ile okur.

Denetleyici, CreatedAtAction çağrısıyla 201 Created yanıtı üretir ve dönen kimliği ürünün detay adresinde kullanır. Yanıt gövdesinde ise istekte alınan ve oluşturulma tarihi ayarlanan ürün nesnesi bulunur. Kod yeni kimliği bu nesnenin Id alanına atamadığı için, oluşturulan kayda gitmek isteyen istemci açısından belirleyici bilgi Location başlığıdır. Böylece SQL’den alınan bir değerin, HTTP yanıtında hangi sorumluluğu üstlendiği açıkça görülür.

Okuma ve yazmada aynı tablo, farklı dönüşler

Listeleme sorgusunda QueryAsync<Product>, seçilen dört sütunu ürün nesnelerine eşler. Kimliğe göre okumada QueryFirstOrDefaultAsync<Product> kullanılır. Eşleşme bulunmazsa denetleyici 404 Not Found döndürür. @Id, @Name ve @Price gibi değerler Dapper’a parametre olarak iletilir. Örneğin detay çağrısındaki kimlik, new { Id = id } nesnesiyle aktarılır. Hangi sütunun okunacağını veya güncelleneceğini SQL metni belirler.

Güncelleme sorgusu CreatedDate alanını da gövdeden gelen değerle yazar. Bu nedenle yalnızca fiyatı değiştiren bir istemcinin dahi mevcut oluşturulma tarihini korumak istiyorsa onu gövdede göndermesi gerekir. SQL metni hangi alanların değişeceğini burada da doğrudan gösterir.

Güncellemede URL’deki kimlik ile istek gövdesindeki kimlik uyuşmadığında API 400 Bad Request döndürür. Güncelleme ve silme sorguları ExecuteAsync ile çalışır ve repository etkilenen satır sayısını servise verir. Denetleyici bu sayıyı değerlendirmediğinden, işlem çağrısı tamamlandığında iki uç nokta da 204 No Content döndürür. Bu davranış, bulunamayan bir kimlikte okuma ile yazma uç noktaları arasındaki farkı somutlaştırır.

Bağlantı ve HTTP işlemleri ayrı yerlerde

DatabaseConnectionFactory, yapılandırmadaki DefaultConnection değerinden bir SqlConnection oluşturur. IDbConnection, repository ve ürün servisi istek kapsamına kaydedilir. Ürün sınıfı alanları tanımlar, uygulama katmanı oluşturulma tarihini belirler, altyapı katmanı SQL’i çalıştırır ve denetleyici HTTP durum kodlarına karar verir. SQL Server bağlantısı ve ürün tablosu hazır olduğunda, geliştirme ortamındaki Swagger arayüzünden bir ürün oluşturup Location adresiyle okumak, bu sorumlulukların birbirine nasıl bağlandığını gösteren kısa bir senaryo sunar.