Yükleniyor

Dapper Kullanım Rehberi: .NET Core Web API Üzerinde Gerçek Zamanlı Uygulama

Blog Kategorileri

.Net Core Mimariler
Tasarım Desenleri
ORM Araçları
API Geliştirme
Web Geliştirme
Veritabanları
14 Nisan 2026 Salı
Dapper Kullanım Rehberi: .NET Core Web API Üzerinde Gerçek Zamanlı Uygulama

Merhaba,

Modern yazılım geliştirme süreçlerinde, .NET Core projelerinin başarısı büyük ölçüde veri erişim katmanının performansına ve yönetilebilirliğine bağlıdır. Günümüzde Entity Framework gibi full-featured ORM araçları standart hale gelse de, hızın ve SQL üzerindeki tam kontrolün kritik olduğu senaryolarda geliştiriciler daha hafif çözümlere ihtiyaç duyar.  Bu noktada, Stack Overflow ekibinin geliştirdiği Dapper, hafif bir Micro-ORM çözümü sunuyor ve minimal yapısıyla öne çıkıyor. Bu yazıda, Dapper’ın .NET Core projelerine entegrasyonunu, mimari konumlandırılmasını ve pratik kullanım senaryolarını detaylıca inceleyeceğiz. 

Dapper nedir? 

Dapper, .NET ekosistemi için geliştirilmiş, açık kaynaklı ve son derece hafif bir mikro ORM kütüphanesidir. Doğrudan ADO.NET altyapısı üzerine inşa edilmiş olan Dapper, veritabanı sorgularını yüksek performanslı bir şekilde nesne modellerine map etme yeteneğine sahiptir.

Neden "Mikro" ORM? Entity Framework gibi kapsamlı araçların aksine Dapper; otomatik change tracking, lazy loading veya karmaşık LINQ-to-SQL çevirileri gibi özellikler sunmaz. Dapper'ın felsefesi basittir: Siz SQL yazarsınız, Dapper ise bu sorguları en hızlı şekilde çalıştırıp sonuçları nesnelere dönüştürür. Bu yaklaşım, veritabanı üzerindeki tüm kontrolü geliştiriciye verirken, soyutlama katmanının getirdiği performans kayıplarını neredeyse sıfıra indirir.

Dapper Ne zaman Kullanılır?

  1. Performans Kritik Uygulamalar: Milisaniyelerin önemli olduğu, yoğun trafik alan sistemlerde.

  2. Karmaşık Raporlama Sorguları: EF'nin üretmekte zorlanabileceği, çoklu join içeren veya özelleştirilmiş SQL fonksiyonları gerektiren kompleks sorgularda.

  3. Mikroservis Mimarileri: Hafif, hızlı ve bağımsız servisler oluştururken.

  4. Mevcut Veritabanları: Şeması üzerinde değişiklik yapamadığınız veya EF modellemesine uygun olmayan eski veritabanlarıyla çalışırken.

  5. Okuma Odaklı Sistemler: CQRS mimarilerinde Read tarafı için en ideal çözümdür.

Dapper Ve Entity Framework

Geliştiriciler arasında en çok merak edilen sorulardan biri şudur: Dapper mı, Entity Framework mü? Profesyonel projelerde cevap genellikle her ikisidir. Dapper ve Entity Framework aynı projede mükemmel bir uyumla çalışabilir. Hibrit Yaklaşım adı verilen bu modelde:

  1. Entity Framework: Veri ekleme güncelleme ve basit CRUD işlemleri ile domain yönetimi için kullanılır.

  2. Dapper: Karmaşık listelemeler, büyük veri setleri ve performans gerektiren raporlama ekranları için tercih edilir.

Bu sayede EF'nin sunduğu geliştirme hızından ödün vermeden, Dapper'ın sunduğu ham performansı kullanmış oluruz.

.Net Core Uygulaması

Dapper, veritabanı şemasını kod üzerinden oluşturma (Code First) yeteneğine sahip değildir. Bu nedenle, önce veritabanımızı hazırlamamız gerekir. Bu örnekte bir ürün yönetim sistemi üzerinden ilerleyeceğiz.

CREATE DATABASE DapperStoreDb;
GO

USE DapperStoreDb;
GO

CREATE TABLE Products(
    Id INT PRIMARY KEY IDENTITY(1,1),
    Name NVARCHAR(200) NOT NULL,
    Price DECIMAL(18,2) NOT NULL,
    Stock INT NOT NULL
);

Appsettings.json Ayarı

Bağlantı bilgilerini güvenli ve yönetilebilir bir şekilde saklamak için appsettings.json dosyasına connection string ekliyoruz:

 "ConnectionStrings": {
   "DefaultConnection": "server = SINAN\\SQLEXPRESS; database = DapperStoreDb; integrated security = true; trustServerCertificate = true"
 },

AppDbContext Sınıfının Oluşturulması

Dapper'da yerleşik bir DbContext yapısı yoktur. Bunun yerine, her veritabanı işlemi için bir IDbConnection örneği oluşturup yöneten bir context sınıfı tasarlamak en iyi pratiktir.

using Microsoft.Data.SqlClient;
using System.Data;

namespace DapperCrud.Context
{
    public class AppDbContext 
    {
        private readonly IConfiguration _configuration;
        private readonly string _connectionString;

        public AppDbContext(IConfiguration configuration)
        {
            _configuration = configuration;
            _connectionString = _configuration.GetConnectionString("DefaultConnection")
                                ?? throw new InvalidOperationException("Bağlantı dizesi bulunamadı.");
        }

        public IDbConnection CreateConnection() => new SqlConnection(_connectionString);
    }
}

DTO Sınıflarının Oluşturulması

Veri transfer nesneleri, API katmanı ile veritabanı modelini birbirinden ayırarak güvenliği ve esnekliği artırır. Dtos/ProductDtos klasörü altında aşağıdaki yapıları kurgulayabiliriz:

namespace DapperCrud.Dtos.ProductDtos
{
    public class AddProductDto
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}
namespace DapperCrud.Dtos.ProductDtos
{
    public class ResultProductByIdDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}
namespace DapperCrud.Dtos.ProductDtos
{
    public class ResultProductDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}
namespace DapperCrud.Dtos.ProductDtos
{
    public class UpdateProductDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public int Stock { get; set; }
    }
}
  1. AddProductDto: Yeni ürün ekleme işlemlerinde kullanılır. Id alanı içermez çünkü veritabanı tarafından üretilir.
  2. UpdateProductDto: Güncelleme işlemleri için kullanılır ve Id alanını içerir.
  3. ResultProductDto: Listeleme işlemlerinde kullanılır.
  4. ResultProductByIdDto: Tek bir ürün detayını döndürmek için kullanılır.

Services Katmanının Oluşturulması

İş mantığını  merkezi bir noktada toplamak için servis katmanını oluşturuyoruz. Bu katman, Dapper sorgularımızın bulunduğu asıl yerdir.

IProductService.cs

using DapperCrud.Dtos.ProductDtos;

namespace DapperCrud.Services.ProductServices
{
    public interface IProductService
    {
        Task AddAsync(AddProductDto product);
        Task UpdateAsync(UpdateProductDto product);
        Task DeleteAsync(int id);   
        Task<ResultProductByIdDto> GetProductByIdDto(int id);
        Task<IEnumerable<ResultProductDto>> GetAllProduct();
    }
}

ProductService.cs

using Dapper;
using DapperCrud.Context;
using DapperCrud.Dtos.ProductDtos;

namespace DapperCrud.Services.ProductServices
{
    public class ProductService(AppDbContext _context) : IProductService
    {
        public async Task AddAsync(AddProductDto product)
        {
            using var connection = _context.CreateConnection();
            var sql = "INSERT INTO Products (Name, Price, Stock) VALUES (@Name, @Price, @Stock)";
            var parameters = new DynamicParameters();
            parameters.Add("@Name", product.Name);
            parameters.Add("@Price", product.Price);
            parameters.Add("@Stock", product.Stock);
            await connection.ExecuteAsync(sql, parameters);
        }

        public async Task DeleteAsync(int id)
        {
            using var connection = _context.CreateConnection();
            var sql = "DELETE FROM Products WHERE Id = @Id";
            var parameters = new DynamicParameters();
            parameters.Add("@Id", id);
            await connection.ExecuteAsync(sql, parameters);
        }

        public async Task<IEnumerable<ResultProductDto>> GetAllProduct()
        {
            var sql = "SELECT * FROM Products";
            using var connection = _context.CreateConnection();
            return await connection.QueryAsync<ResultProductDto>(sql);
        }

        public async Task<ResultProductByIdDto> GetProductByIdDto(int id)
        {
            var sql = "SELECT * FROM Products WHERE Id = @Id";
            using var connection = _context.CreateConnection();
            var parameters = new DynamicParameters();
            parameters.Add("@Id", id);
            return await connection.QueryFirstAsync<ResultProductByIdDto>(sql, parameters);
        }

        public async Task UpdateAsync(UpdateProductDto product)
        {
            using var connection = _context.CreateConnection();
            var sql = "UPDATE Products SET Name = @Name, Price = @Price, Stock = @Stock WHERE Id = @Id";
            var parameters = new DynamicParameters();
            parameters.Add("@Name", product.Name);
            parameters.Add("@Price", product.Price);
            parameters.Add("@Stock", product.Stock);
            parameters.Add("@Id", product.Id);
            await connection.ExecuteAsync(sql, parameters);
        }
    }
}

ExecuteAsync

Bu metot, veri tabanında bir değişiklik (INSERT, UPDATE, DELETE) yapmak istediğinde kullanılır.

  1. Ne Döndürür?: Genellikle etkilenen satır sayısını (int) döndür

  2. Neden Kullanılır?: Geriye bir veri listesi dönmene gerek olmayan, sadece komutun çalıştırılmasının yeterli olduğu durumlar için idealdir.

  3. Örnek: await connection.ExecuteAsync(sql, parametres); satırı, SQL sorgusunu parametrelerle birlikte veri tabanına gönderir ve kaydı ekler/günceller/siler.

QueryAsync

Veri tabanından birden fazla satır çekmek için kullanılır.

  1. Ne Döndürür?: Belirlenen tipte bir koleksiyon (IEnumerable<T>) döndürür.

  2. Neden Kullanılır?: Tablodaki tüm kayıtları veya bir filtreye uyan tüm satırları nesne listesi olarak almak için en hızlı yoldur.

  3. Örnek: connection.QueryAsync<ResultProductDto>(sql) ifadesi, SQL'den gelen her bir satırı otomatik olarak ResultProductDto nesnesine dönüştürür ve bir liste halinde sunar.

QueryFirstAsync

Veri tabanından tek bir satır çekmek için kullanılır.

  1. Ne Döndürür?: Sorgu sonucunda gelen ilk satırı belirlediğin tipte (T) tek bir nesne olarak döndürür.

  2. Önemli Not: Eğer sorgu sonucunda hiçbir kayıt bulunamazsa hata fırlatır. Eğer kaydın bulunmama ihtimali varsa ve hata almak istemiyorsak QueryFirstOrDefaultAsync kullanmak daha güvenlidir çünkü kayıt yoksa null döner)

Neden DynamicParameters Kullanıyoruz? Kodlarımızda fark edebileceğiniz üzere her metodun içinde bir DynamicParameters nesnesi oluşturduk. Bu yaklaşımın temel amacı şunlardır:

  1. SQL Injection Koruması: Parametreleri doğrudan string içine eklemek yerine bu yöntemle geçerek güvenliği en üst düzeye çıkarıyoruz.

  2. Veri Tipi Kontrolü: Gönderilen verilerin SQL tarafındaki karşılıklarını daha kontrollü bir şekilde yönetmemize imkan tanır.

  3. Okunabilirlik: Hangi SQL parametresine hangi DTO alanının atandığını net bir şekilde kod seviyesinde görebiliriz.

İlişkili Verilerle Çalışmak 

Dapper, ilişkili tablolarla çalışırken size Entity Framework'teki gibi karmaşık yapılandırmalar dayatmaz. Her şey yazdığınız SQL sorgusunda biter.

  1. Mantık Çok Basit: Siz SQL tarafında INNER JOIN veya LEFT JOIN kullanarak tabloları birleştirirsiniz, Dapper ise çıkan sonucu sizin belirlediğiniz nesnelere otomatik olarak dağıtır.

  2. Tam Kontrol: Hangi kolonun hangi tabloya ait olduğunu Dapper'a sadece bir virgülle söylemeniz yeterlidir. Bu sayede gereksiz tüm kolonları getirme derdinden kurtulur, sadece ihtiyacınız olan veriyi en hızlı şekilde çekersiniz.

  3. Performans Avantajı: Karmaşık Include sorgularının arkada nasıl bir SQL oluşturduğunu düşünmenize gerek kalmaz. Kendi yazdığınız optimize edilmiş JOIN sorgusu ne kadar hızlıysa, Dapper o kadar hızlıdır.

ProductsController Oluşturulması

API katmanında, dış dünyadan gelen istekleri servis katmanına ileten controller yapısını kurguluyoruz:

using DapperCrud.Dtos.ProductDtos;
using DapperCrud.Services.ProductServices;
using Microsoft.AspNetCore.Mvc;

namespace DapperCrud.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController(IProductService _service) : ControllerBase
    {
        [HttpPost]
        public async Task<IActionResult> Add(AddProductDto model)
        {
            await _service.AddAsync(model);
            return Ok();
        }
        [HttpPut]
        public async Task<IActionResult> Update(UpdateProductDto model)
        {
            await _service.UpdateAsync(model);
            return Ok();
        }
        [HttpDelete]
        public async Task<IActionResult> Delete(int id)
        {
            await _service.DeleteAsync(id);
            return Ok();
        }
        [HttpGet]
        public async Task<IActionResult> GetList()
        {
            var values = await _service.GetAllProduct();
            return Ok(values);
        }
        [HttpGet("{id}")]
        public async Task<IActionResult> GetById(int id)
        {
            var values = await _service.GetProductByIdDto(id);
            return Ok(values);
        }
    }
}

Dependency Injection (DI) Yapılandırması

Son adım olarak, oluşturduğumuz sınıfları .NET Core'un yerleşik DI konteynerine kaydetmemiz gerekiyor. Program.cs dosyasına şu satırları ekliyoruz:

builder.Services.AddScoped<AppDbContext>();
builder.Services.AddScoped<IProductService, ProductService>();

Neden Scoped?

  1. AddScoped<AppDbContext>: Her HTTP isteği için bir nesne oluşturur. İşlem bittiğinde nesne imha edilir. Bu, veritabanı bağlantılarını sızıntı olmadan yönetmek için en güvenli yoldur.

  2. AddScoped<IProductService, ProductService>: Gevşek bağlılık sağlar. İleride servis implementasyonunu değiştirmek isterseniz sadece burayı güncellemeniz yeterli olacaktır.

Dapper Kullanımında Güvenlik

Dapper kullanırken SQL sorgularını manuel olarak yazdığımız için en büyük risk SQL Injection saldırılarıdır. Ancak Dapper, bu riski minimize etmek için bir parametre yönetim mekanizması sunar. Yukarıdaki örneklerimizde kullandığımız DynamicParameters yapısı, verileri sorguya doğrudan gömmek yerine string birleştirme yaparak parameterized query yapısını kullanır.

Önemli Kural: Asla sorgularınızı şu şekilde yazmayın: var sql = "SELECT * FROM Products WHERE Id = " + id; Bu kullanım, kötü niyetli kullanıcıların veritabanınıza sızmasına kapı aralar. Bunun yerine her zaman @Id gibi yer tutucular ve Dapper'ın parametre nesnelerini kullanmalısınız.

Sonuç

.NET Core ekosisteminde veri erişimi dendiğinde akla gelen ilk isim Entity Framework olsa da, Dapper sunduğu hız ve esneklik bakımınıdan güçlü bir araçtır. Minimalist yapısı sayesinde hem öğrenmesi kolaydır hem de veritabanı performansını maksimize etmenize olanak tanır.

Bu yazımızda;

  1. Dapper'ın ne olduğunu ve hangi senaryolarda hayat kurtardığını,

  2. Katmanlı bir mimaride DTO ve Service yapılarıyla nasıl kurgulanabileceğini,

  3. DynamicParameters kullanımıyla güvenli bir CRUD operasyonunun nasıl yazılacağını inceledik.

Eğer projenizde milisaniyelerin hesabı yapılıyorsa veya çok karmaşık SQL sorgularıyla çalışmanız gerekiyorsa, Dapper sizin için en doğru tercih olacaktır. Unutmayın; en iyi çözüm her zaman en popüler olan değil, projenizin ihtiyacına en hızlı ve güvenli cevabı veren çözümdür.

Github: Dapper Project Github | Sinan Tosun

Diğer bloglarımda görüşmek üzere 👋