ORM Kullanmalı mı, Kullanmamalı mı? Dapper vs. EF Core Karşılaştırması
Uzun yıllardır yazılım dünyasında tartışılan en ateşli konulardan biri: "ORM (Object-Relational Mapping) kullanmalı mıyım, yoksa ham SQL mi yazmalıyım?" Bu sorunun net bir cevabı yoktur; doğru cevap, projenizin ihtiyaçlarına, ekibinizin yetkinliklerine ve performans gereksinimlerine bağlıdır.
.NET ekosisteminde, bu iki uç arasında en çok tercih edilen iki araç vardır: Entity Framework Core (EF Core) ve Dapper. EF Core, zengin özellikleri ve geliştirici dostu yapısıyla "ağır" bir ORM'yi temsil ederken; Dapper, ham SQL'e yakın performansı ve basitliğiyle "mikro" bir ORM'yi temsil eder. Bu yazıda, bu iki aracı performans, üretkenlik, esneklik ve öğrenme eğrisi açılarından karşılaştıracak, hangi senaryoda hangisinin tercih edilmesi gerektiğine dair net bir yol haritası sunacağız.
1. Hızlı Karşılaştırma Tablosu
| Kriter | Entity Framework Core (EF Core) | Dapper |
|---|---|---|
| Performans | 🟡 Orta (İyi, ancak Dapper'dan yavaş) | 🟢 Çok Yüksek (Neredeyse Ham SQL) |
| Üretkenlik (Productivity) | 🟢 Çok Yüksek (LINQ, Migration, Change Tracker) | 🟡 Orta (SQL yazmak gerekir) |
| Öğrenme Eğrisi | 🔴 Yüksek | 🟢 Düşük (SQL bilmek yeterli) |
| Şema Yönetimi (Migration) | 🟢 Otomatik (Code-First / Db-First) | 🔴 Yok (Elle yönetilir) |
| Change Tracker (Değişiklik Takibi) | 🟢 Var (Otomatik) | ❌ Yok (Elle güncelleme) |
| LINQ Desteği | 🟢 Tam destek | ❌ Yok |
| İlişki Yönetimi (Navigation) | 🟢 Çok güçlü (Auto-Mapping) | 🟡 Zayıf (Manuel Query) |
| Veritabanı Bağımlılığı | 🟡 Çoklu DB desteği var | 🟢 Çoklu DB desteği var |
| Kod Boyutu | 🟢 Az kod (LINQ) | 🔴 Çok kod (SQL) |
| Hata Ayıklama (Debug) | 🔴 Zor (Üretilen SQL'i görmek gerekir) | 🟢 Kolay (SQL doğrudan görünür) |
| En İyi Olduğu Senaryo | Karmaşık CRUD, Hızlı Geliştirme, Bakım Kolaylığı | Yüksek Performans, Karmaşık Raporlar, Mikroservisler |
2. Entity Framework Core (EF Core): Zengin, Güçlü, Ağır
EF Core, .NET'in resmi ORM'sidir. Geliştiricilere, veritabanını C# nesneleri üzerinden soyutlayan (abstraction) güçlü bir araç sunar. Amacı, veri erişim kodunu neredeyse tamamen ortadan kaldırmaktır.
EF Core'un Süper Güçleri:
-
LINQ Sorguları: SQL değil, C# ile sorgu yazarsınız. Derleme zamanı tip kontrolü (type safety) ve Intellisense desteği vardır.
csharp
var products = await context.Products .Where(p => p.CategoryId == 1 && p.Price > 100) .OrderBy(p => p.Name) .Select(p => new ProductDto { Id = p.Id, Name = p.Name }) .ToListAsync(); -
Change Tracker (Değişiklik Takibi): Nesneler üzerinde yaptığınız değişiklikleri otomatik olarak izler.
SaveChangesAsync()çağrıldığında, sadece değişen alanlar içinUPDATEkomutu oluşturur. -
Migration (Şema Yönetimi): Code-First yaklaşımı ile veritabanı şemasını C# sınıflarından otomatik oluşturabilir ve güncelleyebilirsiniz. Bu, CI/CD pipeline'ları için çok değerlidir.
-
İlişki Yönetimi (Navigation Properties):
Include()ile ilişkili verileri kolayca yükleyebilir (Eager Loading) veyaThenInclude()ile iç içe ilişkileri getirebilirsiniz.
EF Core'un Zayıf Yönleri:
-
Performans: Dapper'a göre daha yavaştır. Özellikle çok sayıda veri çekilen (ör. 100.000 satır) veya karmaşık sorguların olduğu durumlarda fark belirginleşir.
-
Gizli SQL (Hidden SQL): LINQ sorgularının hangi SQL'e dönüşeceğini tahmin etmek bazen zordur. Yanlış yazılmış bir LINQ sorgusu, gereksiz yere tüm tabloyu çekebilir (N+1 sorunu).
-
Öğrenme Eğrisi: LINQ, Change Tracker, Migration gibi kavramları öğrenmek zaman alır. Yanlış kullanım performans sorunlarına yol açabilir.
3. Dapper: Hafif, Hızlı, Şeffaf
Dapper, Stack Overflow tarafından geliştirilmiş bir "mikro ORM"dir. EF Core'un aksine, sizden yönetmesi için sadece IDbConnection nesnesi alır ve ham SQL sorgularını nesnelere eşleştirmek (mapping) için çok hızlı bir yöntem sunar.
Dapper'un Süper Güçleri:
-
Olağanüstü Performans: Dapper, ham SQL'e neredeyse eşit performans gösterir. Çünkü fazladan bir soyutlama (abstraction) katmanı yoktur.
SqlMappersınıfı, IL (Intermediate Language) kod üretimi kullanarak çok hızlı bir mapping yapar. -
Tam Kontrol: SQL sorgusunu kendiniz yazarsınız, böylece tam kontrol sizdedir. Karmaşık JOIN'ler, alt sorgular veya veritabanına özel optimizasyonları doğrudan uygulayabilirsiniz.
-
Öğrenmesi Kolay: Zaten SQL biliyorsanız, Dapper'ı öğrenmek çok kolaydır. Sadece
QueryAsync<T>veyaExecuteAsyncmetotlarını kullanmanız yeterlidir.
Dapper'un Zayıf Yönleri:
-
SQL Bağımlılığı: SQL sorgularını manuel yazmak zorundasınız. Bu, daha fazla kod yazımı ve hata olasılığı demektir. Veritabanı değişikliğinde (ör. SQL Server'dan PostgreSQL'e geçiş), tüm sorguları elden geçirmeniz gerekebilir.
-
Change Tracker Yok: Güncelleme işlemlerinde, hangi alanların değiştiğini kendiniz takip etmek zorundasınız. Her güncelleme için
UPDATEsorgusu yazmanız gerekir. -
Migration Desteği Yok: Veritabanı şema yönetimi için ayrıca bir araç (Flyway, DbUp) kullanmanız gerekir.
-
İlişki Yönetimi Zayıf:
JOINile çekilen verileri, Dapper'ınMulti Mappingözelliği ile manuel olarak nesnelere eşlemeniz gerekir.
4. Performans Testi: Rakamlar Ne Söylüyor?
Yapılan kapsamlı benchmark testlerine göre (ör. Stack Overflow'un kendi testleri), Dapper, EF Core'un yaklaşık 2-3 kat daha hızlı olduğu görülmüştür. Bu fark, özellikle aşağıdaki durumlarda daha da açılır:
-
Sadece Okuma (Read-Only) İşlemleri: Dapper, EF Core'dan ortalama %30-50 daha hızlıdır.
-
Yazma İşlemleri (Insert/Update): EF Core'un Change Tracker'ı devre dışı bırakılsa bile, Dapper daha hızlıdır.
-
Büyük Veri Setleri: 100.000 satır çekildiğinde, Dapper bellek tüketimi ve süre açısından önemli ölçüde daha iyi performans gösterir.
Ancak unutmayın: Bu performans farkı, her uygulamada hissedilmez. Çoğu web uygulamasının %95'i, EF Core'un sağladığı hızla rahatça çalışabilir. Fark, gelen isteklerin %1'inden daha azını oluşturan kritik sorgularda ortaya çıkar.
5. Ne Zaman Hangi Araç?
EF Core Seçin:
-
Hızlı Geliştirme (RAD): Projenin veri erişim katmanını hızlıca oluşturmak istiyorsanız.
-
Karmaşık CRUD: Çok sayıda tablo arasında ilişki (navigation) yönetimi ve nested include'lar gerekiyorsa.
-
Ekip Tecrübesi: Ekip LINQ ve EF Core'a zaten aşinaysa.
-
Migration Yönetimi: Code-First ile veritabanı şemasını CI/CD içinde yönetmek istiyorsanız.
-
Düşük/Orta Ölçekli Trafik: Saniyede 100-200 istekten fazla değilse.
Dapper Seçin:
-
Yüksek Performans: Mikrosaniye (microsecond) seviyesinde cevap süresi gerekiyorsa.
-
Karmaşık Sorgular: Çok sayıda JOIN, alt sorgu veya veritabanına özel (window functions) sorgular yazmanız gerekiyorsa.
-
Mikroservisler: Her servisin kendi veritabanına sahip olduğu mimarilerde.
-
SQL Uzmanlığı: Ekibiniz SQL konusunda zaten çok güçlüyse.
-
EF Core Yavaş Kalıyorsa: EF Core'un ürettiği sorgular yeterince optimize değilse (performans testleri ile tespit edilir).
6. Hibrit Yaklaşım: "Polyglot Persistence"
En iyi yaklaşım, her iki aracı da kullanmaktır. Bu, "Polyglot Persistence" olarak adlandırılır.
-
CRUD (Create, Read, Update, Delete) İşlemleri: Standart ve basit sorgular için EF Core kullanın (hızlı geliştirme, migration).
-
Raporlama / Kompleks Sorgular: Yüksek performans gerektiren, karmaşık sorgular için Dapper kullanın.
csharp
// Hibrit Kullanım Örneği
public class ProductService
{
private readonly AppDbContext _context; // EF Core
private readonly IDbConnection _connection; // Dapper
public ProductService(AppDbContext context, IDbConnection connection)
{
_context = context;
_connection = connection;
}
// Basit CRUD -> EF Core
public async Task AddProductAsync(Product product)
{
await _context.Products.AddAsync(product);
await _context.SaveChangesAsync();
}
// Karmaşık Rapor -> Dapper
public async Task<IEnumerable<ProductSalesDto>> GetProductSalesReportAsync(DateTime start, DateTime end)
{
var sql = @"
SELECT p.Id, p.Name, SUM(o.Quantity) as TotalSold
FROM Products p
JOIN OrderItems o ON p.Id = o.ProductId
JOIN Orders ord ON o.OrderId = ord.Id
WHERE ord.OrderDate BETWEEN @Start AND @End
GROUP BY p.Id, p.Name
ORDER BY TotalSold DESC";
return await _connection.QueryAsync<ProductSalesDto>(sql, new { Start = start, End = end });
}
}
7. Sık Yapılan Hatalar ve İpuçları
| Hata | Çözüm |
|---|---|
EF Core ile N+1 sorunu yaşamak (döngü içinde Include kullanmamak) |
Include() veya ThenInclude() kullanın veya AsSplitQuery() ile performansı artırın. |
| Dapper ile manuel mapping'de veri tipi uyuşmazlığı | QueryAsync<T>'deki T tipi ile SQL'deki sütun adları ve tipleri birebir eşleşmeli. |
EF Core'da AsNoTracking() kullanmamak |
Sadece okuma işlemlerinde AsNoTracking() kullanarak Change Tracker yükünü azaltın. |
Dapper'da SqlMapper'in cache'ini temizlememek |
Dapper, query'leri cache'ler. Uzun süre çalışan uygulamalarda cache şişebilir. |
Her sorguda yeni IDbConnection açıp kapatmak |
IDbConnection'ı DI (Dependency Injection) ile Scoped veya Transient olarak yönetin. |
Sonuç:
Dapper ve EF Core arasındaki seçim, "ya hep ya hiç" değildir. Her ikisi de güçlü araçlardır ve amaçları farklıdır. EF Core, geliştirme hızını, bakım kolaylığını ve üretkenliği hedeflerken; Dapper, ham performans ve kontrolü hedefler.
Karar Verme Kriterleriniz:
-
Ekibinizin Yetkinlikleri: SQL mi, LINQ mu daha aşinalar?
-
Uygulamanın Performans Gereksinimleri: Saniyede kaç istek geliyor? Gecikme (latency) ne kadar kritik?
-
Veritabanı Karmaşıklığı: Basit tablolar mı yoksa onlarca JOIN'li sorgular mı?
-
Proje Ömrü: Uzun vadeli bir proje mi (EF Core avantaj sağlar) yoksa hızlı bir prototip mi?
Unutmayın, hibrit yaklaşım (EF Core + Dapper) çoğu projede en doğru dengeyi sağlar. Sorgunuz basitse EF Core'u, karmaşık ve performans kritikse Dapper'ı kullanın. Bu sayede, hızlı geliştirme avantajından vazgeçmeden, kritik noktalarda maksimum performansı elde edebilirsiniz.