Entity Framework Değil Sorgularınız Hantal!
Herkese merhaba! Bugün sizinle geliştirici dünyasında sıkça duyduğumuz, ancak bir o kadar da yanlış anlaşılan bir konuyu konuşacağız: EF Core ve performans. Bazılarımızın diline pelesenk olmuş bir söz var: “EF Core yavaş abi!” E, tamam da gerçekten yavaş mı, yoksa aslında biraz da biz mi hantallık yapıyoruz? Hadi gelin, bunun biraz dedikodusunu yapalım.
Biliyorsunuz, modern ORM araçları biz geliştiricilere hayatı kolaylaştırmak için var. Ama işin aslı şu ki, doğru kullanılmazsa, hayatımızı cehenneme çevirmeleri işten bile değil. Bir düşünün; tek bir kullanıcının bilgisine ulaşmak isterken tüm kullanıcı tablosunu belleğe çekiyorsunuz. Sonra da “EF Core kötü” diyorsunuz. Kendi hatalarımıza bakmadan suçu zavallı ORM’e atıyoruz, değil mi? :)
Bu yazıda, EF Core’da performansı artırmak için kullanabileceğiniz pratik yöntemlerden bahsedeceğiz. İşte bu yöntemlerle, “N+1 sorgu problemi” gibi klişe hatalara düşmeden, uygulamalarınızın turbo hızına ulaşmasını sağlayabilirsiniz. Hem siz rahat edin, hem de sunucunuz size teşekkür etsin.
Hazır mıyız? Öyleyse sorgularınızı kollarından tutun, onları hantallıktan kurtarıyoruz! 🚀
1. AsNoTracking Kullanılmaması
Hata
Salt okunur sorgularda AsNoTracking
kullanılmadığında EF Core, sorgu sonuçlarını izleme mekanizmasına dahil eder. Bu da gereksiz kaynak tüketimine yol açar.
Örneğin; Bir yönetim panelinde tüm kullanıcıların listesini göstermek istediğinizi düşünelim. Burada kullanıcıları yalnızca görüntüleyeceksiniz; herhangi bir güncelleme, silme veya ekleme işlemi yapılmayacak.
var users = await context.Users.ToListAsync(); // İzleme yapılır.
Neden Problem?
- EF Core, her bir
User
nesnesini "izlemeye" alır (tracking). - İzleme, bellekte bir takip tablosu oluşturur ve
ChangeTracker
mekanizması devreye girer. - Kullanıcı sayısı fazla olduğunda (örneğin 10.000 kullanıcı), bu işlem bellek tüketimini artırır ve sorgunun çalışmasını yavaşlatır.
Sonuç
- Kullanıcılar yalnızca görüntülenecekse, izleme işlemi tamamen gereksizdir.
- Bellekte büyük bir yük oluşturur ve uygulamanızın performansını düşürür.
- Özellikle veritabanı bağlantı havuzunda (connection pool) yük oluşmasına neden olabilir.
Çözüm
AsNoTracking
kullanarak izlemeyi devre dışı bırakabilirsiniz.
var users = await context.Users.AsNoTracking().ToListAsync(); // İzleme yapılmaz.
Neden Daha İyi?
- İzleme işlemi devre dışı bırakıldığından kaynak tüketimi azalır.
- Büyük veri setlerinde gözle görülür performans iyileştirmesi sağlar.
2. Gereksiz Kolonların Sorgulanması
Hata
Tüm kolonların sorgulanması yerine sadece ihtiyaç duyulan kolonları sorgulamak.
Örneğin; Bir yönetim panelinde, kullanıcıların sadece isim ve e-posta bilgilerini göstermek istediğinizi düşünelim. Ancak tablonuzda kullanıcılarla ilgili birçok başka bilgi var: adres, telefon, profil resmi URL’si, doğum tarihi, kayıt tarihi, vb.
var users = await context.Users.ToListAsync(); // Tüm kolonlar belleğe alınır.
Neden Problem?
- Büyük tablodaki gereksiz kolonlar belleğe yüklenir ve ağ trafiği artar. Dolayısıyla kullanıcı tablosundaki tüm kolonları sorgulamak, tablo büyükse veri tabanı trafiğine ve bellekte aşırı kaynak tüketimine neden olur.
- Aşağıdaki gibi bir tablo düşünün:
- Users
- Id
- Name
- Email
- Address
- Phone
- ProfilePictureUrl
- DateOfBirth
- RegistrationDate - Eğer her kullanıcı için sadece
Name
veEmail
bilgisine ihtiyacınız varsa, geri kalan kolonlar sorguda tamamen gereksizdir.
Sonuç
- 1.000.000 kullanıcıyı çeken bir sorgu, her bir satır için 10 kolon içeriyorsa, 10.000.000 veri parçası belleğe yüklenir. Bu da veritabanı trafiğini arttırır ve sorgu sürelerini uzatır.
Çözüm
Select
ile yalnızca ihtiyaç duyulan kolonları sorgulayın.
var users = await context.Users
.Select(u => new { u.Name, u.Email }) // Sadece gerekli kolonlar
.ToListAsync();
Neden Daha İyi?
- Sadece
Name
veEmail
bilgisi veri tabanından çekilir. - Gereksiz kolonlar sorguya dahil edilmez.
- Daha az veri taşındığı için veri tabanı trafiği ve bellek kullanımı azalır.
- Query execution süresi kısalır.,
3. N+1 Sorgu Problemi
Hata
Lazy Loading ile her döngüde ayrı bir sorgu çalıştırılması.
Örneğin; Bir e-ticaret uygulamasında, kullanıcıların siparişlerini listelemek istediğinizi düşünün. Kullanıcılar ve siparişler arasında bir “Bire Çok (1-*)” bir ilişki var. Yani her kullanıcı birden fazla siparişe sahip olabilir.
Tablo Yapısı
Users
tablosu: Kullanıcı bilgilerini içerir.Orders
tablosu: Kullanıcıların siparişlerini içerir veUserId
ileUsers
tablosuna bağlanır.
var users = await context.Users.ToListAsync(); // Tüm kullanıcılar çekiliyor
foreach (var user in users)
{
var orders = user.Orders.ToList(); // Her kullanıcı için ayrı bir sorgu çalıştırılıyor!
}
Neden Problem?
- İlk sorgu, tüm kullanıcıları çekmek için veri tabanına bir sorgu gönderir:
SELECT * FROM Users;
- Daha sonra, her kullanıcı için ilişkili siparişleri almak için bir sorgu çalıştırılır. Örneğin, 100 kullanıcı varsa, 100 ek sorgu çalışır:
SELECT * FROM Orders WHERE UserId = 1;
SELECT * FROM Orders WHERE UserId = 2;
...
SELECT * FROM Orders WHERE UserId = 100;
Sonuç
- Veri tabanına toplamda 1 (Users) + 100 (Orders) = 101 sorgu gönderilir.
- Bu da ağ trafiğini artırır ve uygulamanın performansını ciddi şekilde düşürür.
- Bu durum “N+1 Sorgu Problemi” olarak bilinir ve performansı ciddi şekilde düşürür.
- Veri tabanına çok fazla sorgu gönderilir.
Çözüm
Include
kullanarak ilişkili verileri tek sorguda çekin. Yani hem kullanıcıları hem de onların siparişlerini tek sorguda çekebilirsiniz.
var usersWithOrders = await context.Users
.Include(u => u.Orders) // İlişkili siparişleri önceden yüklüyoruz
.ToListAsync();
Oluşturulan SQL:
SELECT Users.*, Orders.*
FROM Users
LEFT JOIN Orders ON Users.Id = Orders.UserId;
Neden Daha İyi?
- Tek bir sorguda hem kullanıcıları hem de ilişkili siparişleri çeker.
- Tüm kullanıcılar ve siparişleri aynı anda belleğe alınır.
- Veri tabanı bağlantılarını minimize eder.
Çıktı (Gerçek Hayattan Verilerle)
- Lazy Loading: 1200 ms (101 sorgu)
- Include: 300 ms (1 sorgu)
4. Eager Loading’in Varsayılan Olması ve Performans Sorunları
Bazı projelerde, EF Core’da Eager Loading varsayılan olarak ayarlanmış olabilir. Yani, bir Entity
sorgulandığında, o entity'nin tüm ilişkili Navigation Property
'leri otomatik olarak yüklenir.
Eager Loading Nedir?
- Eager Loading, bir entity sorgulandığında, ilişkili tüm verilerin de aynı anda veri tabanından çekilmesini sağlar.
Include
gibi yöntemlerle manuel olarak yapılabileceği gibi, projede otomatik olarak tüm navigasyon özelliklerinin yüklenmesi şeklinde yapılandırılabilir.
Hata
Eager Loading’in varsayılan olması ve bir entity veritabanından çekilirken onunla ilişkili tüm entitylerin de bu veri ile birlikte çekilmesi.
Örneğin; Bir e-ticaret projesinde kullanıcıların temel bilgilerini göstermek istiyorsunuz. Ancak kullanıcıların Orders
ve Addresses
gibi ilişkili tabloları da var.
Neden Problem?
Eager Loading’in varsayılan olarak etkin olduğu bir projede, şu gibi bir kod çalıştırdığınızda:
var users = await context.Users.ToListAsync(); // Sadece kullanıcılar lazım
Users
sorgulaması yapılırken ilişkili olan tüm Orders
ve Addresses
de otomatik olarak yüklenir. Bunun sonucunda EF Core şu şekilde bir sorgu oluşturur:
SELECT Users.*, Orders.*, Addresses.*
FROM Users
LEFT JOIN Orders ON Users.Id = Orders.UserId
LEFT JOIN Addresses ON Users.Id = Addresses.UserId;
Kullanıcıların sadece Name
ve Email
bilgisine ihtiyacınız olsa bile, tüm sipariş ve adres verileri gereksiz yere belleğe alınır. Bu da gereksiz veri trafiği ve bellek tüketimi oluşturur.
Sonuç
- Gereksiz Veri Taşınması: Veritabanından çok fazla gereksiz veri çekilir.
- Performans Düşüşü: Sorgu süresi uzar ve uygulama bellek tüketimi artar.
- Ağ Trafiği: Büyük veri transferleri ağ yükünü artırır.
Çözüm
Lazy Loading
veya Explicit Loading
kullanarak ilişkili verileri tek sorguda çekin. Yani hem kullanıcıları hem de onların siparişlerini tek sorguda çekebilirsiniz.
Eğer ilişkili verilere her zaman ihtiyaç duymuyorsanız, Lazy Loading
veya Explicit Loading
kullanarak bu sorunları çözebilirsiniz.
4.1. Lazy Loading: İlişkilere Gerektiğinde Erişim
virtual
keyword'ü, EF Core'da Lazy Loading'i etkinleştirmek için kullanılan önemli bir anahtar kelimedir. virtual
, navigasyon özelliklerinin EF Core tarafından proxy sınıflar ile dinamik olarak genişletilmesine olanak tanır. Bu sayede, ilişkili veriler yalnızca ihtiyaç duyulduğunda, yani ilgili navigasyon özelliğine erişildiğinde yüklenir.
4.1.1 virtual'ın Lazy Loading için İşlevi
- Proxy Sınıflar (Dynamic Proxies): EF Core, navigasyon özelliklerini
virtual
olarak işaretlediğinizde bu sınıfları dinamik olarak genişleterek proxy sınıflar oluşturur. - Ertelenmiş Veri Yükleme: Proxy sınıflar, bir navigasyon özelliğine erişilene kadar ilişkili verilerin yüklenmesini erteler.
- Otomatik Yükleme: Navigasyon özelliği çağrıldığında, EF Core ilişkili verileri veri tabanından otomatik olarak çeker.
Modellerimizin aşağıdaki gibi olduğunu varsayalım:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; } // Lazy Loading için virtual
}
public class Order
{
public int Id { get; set; }
public int UserId { get; set; }
public string ProductName { get; set; }
public virtual User User { get; set; } // Lazy Loading için virtual
}
Örnek koda bakalım:
var user = await context.Users.FirstOrDefaultAsync(u => u.Id == 1);
if (user != null)
{
foreach (var order in user.Orders)
{
Console.WriteLine($"Order ID: {order.Id}, Product: {order.ProductName}, Price: {order.Price}");
}
}
4.1.2. virtual Kullanılmazsa Ne Olur?
- EF Core proxy sınıf oluşturamaz.
- Navigasyon özellikleri Lazy Loading yerine null döner.
- Eğer Lazy Loading’e ihtiyaç yoksa veya tüm ilişkili verileri baştan yüklemek istiyorsanız
Include
veyaExplicit Loading
tercih edebilirsiniz.
4.1.3. virtual Keyword’ü ile Lazy Loading Aktif Olursa Ne Olur?
user.Orders
çağrıldığında, EF Core proxy sınıfını kullanarak ilişkiliOrders
verilerini veri tabanından çeker.- İlk
Users
sorgusundan sonra ikinci bir sorgu gönderilir.
SELECT * FROM Orders WHERE UserId = 1;
4.1.4. Lazy Loading ile virtual’ın Teknik İşleyişi
- EF Core, proxy sınıfını dinamik olarak oluşturur.
User
sınıfı yerine EF Core, arka planda bir proxy sınıfı türetir (örneğin,UserProxy
).- Bu proxy sınıf, navigasyon özelliklerini (
Orders
) kontrol eder ve gerektiğinde veri tabanına sorgu gönderir.
2. Proxy sınıf, Orders
özelliğine erişildiğinde, EF Core’un Lazy Loading mekanizmasını çalıştırır.
4.1.5. Ne Zaman virtual Kullanmalıyım?
- Lazy Loading Gerekliyse: Navigasyon özelliklerinin ihtiyaç duyulduğunda otomatik olarak yüklenmesini istiyorsanız
virtual
kullanmalısınız. - Performans İyileştirmesi: Yalnızca eriştiğiniz verilere odaklanmak ve gereksiz veri tabanı trafiğinden kaçınmak istiyorsanız uygundur.
4.1.6. Ne Zaman virtual Kullanılmamalı?
- Lazy Loading’e İhtiyaç Yoksa:
- Tüm ilişkili verileri baştan yüklemek istiyorsanız
Include
veyaExplicit Loading
tercih edin.
2. Performans Kaygısı Varsa:
- Lazy Loading, döngülerde veya yanlış kullanımda N+1 Sorgu Problemi yaratabilir.
3. Netlik İstiyorsanız:
- Lazy Loading, sorguların ne zaman çalıştığını gizleyebilir. Veritabanı sorgularını daha net kontrol etmek istiyorsanız
virtual
'ı kaldırabilirsiniz.
4.1.7. Avantajlar
- Gereksiz Veriyi Önler: İlişkili verilere ihtiyaç yoksa veri tabanından çekilmez.
- Bellek Tasarrufu: Yalnızca erişilen veriler yüklendiği için bellekte daha az yer kaplar.
- Kod Temizliği: İlişkili verilerin ne zaman yükleneceğiyle ilgili ekstra kod yazmayı gerektirmez.
- Dinamik Veri Yükleme: Sorgu sırasında belirli ilişkili verilere ihtiyaç duyulursa otomatik olarak yükler.
4.1.8. Dezavantajlar
- N+1 Sorgu Problemi: Döngülerde ilişkili verilere erişildiğinde her ilişki için ayrı sorgu çalıştırabilir.
- Performans Sorunları: Birden fazla küçük sorgu çalıştırıldığı için büyük veri setlerinde yavaşlık yaratabilir.
- Sorgu Kontrolünün Kaybı: Hangi verilerin sorgulandığını anlamak zorlaşır; beklenmedik sorgular oluşabilir.
- Ek Proxy Yükü: EF Core, proxy sınıflar oluşturur, bu da bazen gereksiz karmaşıklık yaratabilir.
- Konfigürasyon Gerekliliği:
UseLazyLoadingProxies
vevirtual
gibi özelliklerin doğru yapılandırılması gerekir.
4.2. Explicit Loading: İlişkileri Elle Yükleme
Explicit Loading, Lazy Loading’e benzer ancak ilişkili verilerin elle yüklenmesini sağlar. Bu, daha fazla kontrol sunar.
4.3. Eager Loading’i Gerektiğinde Kullanma
Eğer ilişkili verilerin gerçekten gerekli olduğu bir durumda Eager Loading kullanmak istiyorsanız, bu işlemi manuel olarak yapmalısınız:
var usersWithOrders = await context.Users
.Include(u => u.Orders) // Sadece gerekli ilişkiler yüklenir
.ToListAsync();
Bu şekilde, kontrol tamamen sizde olur ve hangi ilişkilerin yükleneceğini siz belirlersiniz.
Eager Loading Neden Daha Kötü?
- Eager Loading, kontrolsüz bir şekilde kullanıldığında performansı düşürebilir.
- Her sorguda tüm ilişkilerin yüklenmesi gereksiz veri trafiğine yol açar.
Temelde 4. ve 3. maddeler birbirine benzerdir ancak iki konuya da ayrı ayrı değinme ihtiyacı hissettim.
5. Büyük Veri Setlerini Sayfalama Yapmadan Belleğe Yüklemek
Hata
Büyük veri setlerini belleğe yüklemek, genellikle gereksiz bellek tüketimine ve performans sorunlarına yol açar. Gerçek hayatta bu durum genellikle listeler, raporlar, veya filtrelenmemiş sorgular için ortaya çıkar. Özetle, sayfalama yapılmadan büyük bir veri setini belleğe almak bir sorundur.
Örneğin; Bir e-ticaret sisteminde, tüm siparişlerin bir raporunu oluşturmak istediğinizi düşünelim. Orders
tablonuzda aşağıdaki gibi yüz binlerce kayıt var:
Id | CustomerId | ProductName | Quantity | Price | OrderDate | Status
----------------------------------------------------------------------------
1 | 101 | Laptop | 1 | 1200 | 2024-12-01 | Shipped
2 | 102 | Headphones | 2 | 200 | 2024-12-02 | Pending
... (1,000,000 kayıt daha)
Bu rapor için tüm siparişleri veri tabanından çektiğinizi düşünelim:
var orders = await context.Orders.ToListAsync(); // Tüm siparişler belleğe yükleniyor
foreach (var order in orders)
{
Console.WriteLine($"{order.Id}, {order.ProductName}, {order.Price}");
}
Neden Problem?
- Bellek tüketimi artar.
Orders
tablosunda 1 milyon kayıt varsa, tüm kayıtlar belleğe yüklenir. - Büyük veri setlerinde uygulama yavaşlar veya çökebilir. Çünkü örneğin her bir kayıt 1 KB boyutundaysa, bu 1 GB bellek tüketimine neden olur.
Sonuç
- Bellek aşırı yüklenir.
- Performans düşer.
Çözüm
Skip
ve Take
metotları yardımıyla sayfalama (pagination) ile veriyi parça parça çekin.
int pageSize = 100; // Bir seferde çekilecek kayıt sayısı
int pageNumber = 1;
var pagedOrders = await context.Orders
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
foreach (var order in pagedOrders)
{
Console.WriteLine($"{order.Id}, {order.ProductName}, {order.Price}");
}
Skip
veTake
ile her sayfada yalnızcapageSize
kadar kayıt çekilir.- Kullanıcı bir sonraki sayfayı istediğinde
pageNumber
artırılır ve sonraki kayıtlar getirilir.
Oluşturulan SQL:
SELECT * FROM Orders
ORDER BY Id
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;
Neden Daha İyi?
- Bellek tüketimi kontrol altına alınır.
- Veri tabanından sadece ihtiyaç duyulan adet kadar kayıt çekilir.
6. Filtre Kullanılmaması
Hata
Tüm veri belleğe alındıktan sonra filtre uygulanması.
Örneğin; Bir e-ticaret uygulamasında, yalnızca “Shipped” durumundaki siparişleri listelemek istediğinizi düşünelim. Ancak tüm siparişleri sorgulayıp, filtrelemeyi bellek üzerinde yapıyorsunuz:
var orders = await context.Orders.ToListAsync(); // Tüm siparişler belleğe alınıyor
var shippedOrders = orders.Where(o => o.Status == "Shipped").ToList(); // Filtreleme bellekte yapılıyor
Neden Problem?
Orders
tablosundaki tüm veriler belleğe yüklenir, gereksiz veri tabanı trafiği ve bellek tüketimi oluşur.- Tablo çok büyükse (örneğin 1 milyon kayıt), bellek tüketimi artar ve uygulamanız yavaşlar veya çökebilir.
Sonuç
- Performans düşer.
- Bellek sınırı aşılabilir.
Çözüm
Filtreyi sorgu sırasında yani veritabanı düzeyinde uygulayın.
var shippedOrders = await context.Orders
.Where(o => o.Status == "Shipped") // Filtreleme burada yapılıyor
.ToListAsync();
Üretilen SQL:
SELECT * FROM Orders WHERE Status = 'Shipped';
Unutmayın, yukarıdaki kodda ToListAsync metotu çağrılmadan db’ye sorgu gönderilmez!
Neden Daha İyi?
- Sorgu veritabanında optimize edilir ve yalnızca filtrelenmiş sonuçlar veri tabanından alınır.
- Sadece gerekli kayıtlar belleğe yüklenir ve gereksiz bellek tüketimi ve ağ trafiği önlenir.
- Veritabanı motorunun optimizasyonları devreye girer.
Gerçek Hayatta Fark
Hatalı Yaklaşım: 1 milyon siparişlik bir tabloda tüm veriyi çekip, yalnızca “Shipped” durumunda olan 10.000 siparişi bellekte filtrelerseniz:
- Bellek Kullanımı: 1 milyon kayıt için gereksiz tüketim.
- Ağ Trafiği: Tüm kayıtların sunucu ile veri tabanı arasında taşınması.
- Performans: Bellek sınırı aşılabilir, uygulama yavaşlar.
Doğru Yaklaşım: Yalnızca “Shipped” durumunda olan 10.000 sipariş çekilir:
- Bellek Kullanımı: Sadece ihtiyaç duyulan veri.
- Ağ Trafiği: Minimum veri transferi.
- Performans: Çok daha hızlı ve verimli.
7. Transaction’ların Yanlış Kullanımı
Hata: Küçük işlemler için gereksiz yere transaction kullanımı.
Örneğin; Bir e-ticaret uygulamasında, bir müşterinin sipariş vermesiyle birlikte:
- Sipariş bilgisi
Orders
tablosuna eklenir. - Siparişteki ürünlerin stoğu
Products
tablosunda güncellenir.
Bu işlem bir transaction ile gerçekleştirilmelidir, çünkü her iki adımın da başarılı olması gerekir. Ancak transaction’ları gereksiz yere kullanmak veya yanlış yapılandırmak performans sorunlarına ve kilitlenmelere yol açabilir.
Hatalı Kullanım 1: Gereksiz Transaction Kullanımı
Gereksiz bir şekilde basit bir INSERT işlemi için transaction kullanıldığını düşünelim:
using var transaction = await context.Database.BeginTransactionAsync();
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync();
await transaction.CommitAsync();
Sorun:
- Transaction Gereksiz: Bu işlem zaten atomic’dir (tek sorgu) ve EF Core bunu kendi içinde yönetir.
- Ekstra Yük: Transaction, veri tabanında ek kilitlenmelere neden olur ve sistem kaynaklarını boşa tüketir.
- Performans Kaybı: Transaction başlatma, commit etme ve yönetme işlemleri gereksiz yere ekstra işlem süresi ekler.
Doğru Yaklaşım: Transaction olmadan SaveChangesAsync
kullanmak yeterlidir:
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync(); // Tek adımda işlem tamamlanır
Hatalı Kullanım 2: Birden Fazla Transaction Kullanmak
Aynı işlemi gerçekleştiren birden fazla transaction kullanmak gereksiz karmaşıklığa yol açar.
using var transaction1 = await context.Database.BeginTransactionAsync();
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync();
await transaction1.CommitAsync();
using var transaction2 = await context.Database.BeginTransactionAsync();
var product = await context.Products.FindAsync(101);
product.Stock -= 2;
context.Products.Update(product);
await context.SaveChangesAsync();
await transaction2.CommitAsync();
Sorun:
- Transaction Yönetimi Zorlaşır: İki ayrı transaction kullanmak gereksiz karmaşıklık yaratır.
- Tutarlılık Sorunu: İlk transaction başarılı olur, ikinci transaction başarısız olursa sistem tutarsız hale gelir.
Doğru Yaklaşım: Tek bir transaction kullanarak hem sipariş ekleme hem de stok güncelleme işlemlerini birlikte gerçekleştirin:
using var transaction = await context.Database.BeginTransactionAsync();
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync();
var product = await context.Products.FindAsync(101);
product.Stock -= 2;
context.Products.Update(product);
await context.SaveChangesAsync();
await transaction.CommitAsync();
Avantaj:
- Tüm işlemler tek bir transaction içinde gerçekleşir.
- Eğer bir adım başarısız olursa tüm işlem geri alınır.
Hatalı Kullanım 3: Transaction’ı Uzun Süre Açık Tutmak
Transaction’lar uzun süre açık kaldığında veri tabanında kilitlenmelere yol açabilir. Örneğin, bir API çağrısı içinde transaction başlatıp gereksiz beklemelerle işlemi tamamlamak:
using var transaction = await context.Database.BeginTransactionAsync();
// Kullanıcıdan onay bekleniyor (örneğin, bir API çağrısı)
await Task.Delay(10000); // 10 saniye bekleme
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync();
await transaction.CommitAsync();
Sorun:
- Kilitlenme: Transaction açıkken veri tabanındaki ilgili tablolar kilitlenir. Başka işlemler bu tabloları kullanamaz.
- Performans Kaybı: Uzun süreli transaction’lar veri tabanında performansı düşürür.
Doğru Yaklaşım: Transaction’ları yalnızca gerekli olduğu kadar kısa süre açık tutun:
using var transaction = await context.Database.BeginTransactionAsync();
var order = new Order
{
CustomerId = 1,
ProductId = 101,
Quantity = 2,
OrderDate = DateTime.UtcNow
};
await context.Orders.AddAsync(order);
await context.SaveChangesAsync();
await transaction.CommitAsync();
Neden Problem?
- Transaction başlatma ve yönetimi ek yük getirir.
- Veri tabanında kilitlenmelere yol açabilir.
Gerçek Hayattaki Sorunlar
- Gereksiz Kilitlenmeler: Örneğin, yüksek trafikli bir sistemde transaction’lar uzun süre açık kalırsa, diğer işlemler kilitlenebilir.
- Performans Düşüşü: Her transaction başlatma ve commit işlemi, veri tabanı üzerinde ek işlem gerektirir.
- Tutarsız Veri: Bir transaction başarılı olurken diğer transaction başarısız olursa, veri tabanında tutarsızlık oluşur.
Öneriler
Transaction kullanımı, doğru şekilde yapılandırıldığında veri tabanı işlemlerinin tutarlılığını sağlar. Ancak:
- Gereksiz yere transaction kullanmaktan kaçının.
- Tek transaction içinde birden fazla işlemi yönetin.
- Transaction’ları kısa süre açık tutun.
Bu yaklaşımlar, veri tabanı performansını artırır ve uygulamanızın stabil çalışmasını sağlar.
BONUS: REN Helpers ile Veritabanı İşlemlerini Standartlaştırın!
EF Core kullanırken performansı artırmak, kod tekrarını azaltmak ve daha temiz bir mimari oluşturmak için doğru araçları seçmek önemlidir. Eğer siz de her yeni proje için Repository ve Unit Of Work sınıfları yazarken yukarıdaki gibiyseniz REN Helpers ile tanışın🚀
REN Helpers Nedir?
REN Helpers, modern yazılım geliştirme süreçlerinde sıkça ihtiyaç duyulan Repository, Unit of Work, ve Cache gibi temel yapı taşlarını sunan bir NuGet paketidir. REN (Regular Everyday Normal), kodunuzu düzenler, hızlandırır ve standartlaştırır.
Neler Sunar?
- Önceden Tanımlanmış Repository ve Unit of Work Sınıfları:
- Standart DB İşlemleri: CRUD işlemleri, filtreleme, ve veritabanı operasyonları için hazır sınıflar.
- Transaction Yönetimi: Repository ve Unit of Work sınıflarını kullanarak işlemlerinizde tam kontrol sahibi olun.
2. Cache Yönetimi:
- Predefined Cache Sınıfları: Hızlı ve kolay cache işlemleri için hazır yapılar.
- Özelleştirilebilir ve Genişletilebilir: Kendi cache mantığınızı kolayca ekleyebilirsiniz.
3. Esneklik ve Genişletilebilirlik:
- Override ve Özelleştirme: Hazır gelen sınıfları olduğu gibi kullanabilir veya projenize uygun şekilde genişletebilirsiniz.
4. Zaman Tasarrufu: Sıkça ihtiyaç duyulan işlevleri sıfırdan yazmak yerine REN Helpers’ın sunduğu araçları kullanarak daha az zamanda daha çok iş yapabilirsiniz.
5. Kod Tutarlılığı: Standart yapı taşlarını kullanarak ekibinizle daha uyumlu bir şekilde çalışabilirsiniz.
6. Mimari Temizlik: REN Helpers ile kodunuz daha düzenli, okunabilir ve sürdürülebilir hale gelir.
7. EF Core ile Uyum: REN Helpers, EF Core ile kolayca entegre olur ve performans optimizasyonlarınıza katkı sağlar.
RENHelpers’a bir şans vermek isterseniz;
Github: https://github.com/TekyaygilFethi/RENHelpers
NuGet Gallery: https://www.nuget.org/packages/RENHelpers.DataAccessHelpers
Dökümantasyon: https://fethis-organization.gitbook.io/ren-regular-everyday-normal-helper
Kapanış: Performans ve Verimlilik Artık Sizde!
Gördüğünüz gibi, EF Core’un hantallığı aslında bizim yanlış kullanım alışkanlıklarımızdan kaynaklanıyor olabilir. Doğru teknikleri ve optimizasyonları uyguladığınızda, EF Core yalnızca güçlü değil, aynı zamanda son derece hızlı bir araç haline gelir. Artık “EF Core yavaş” demek yerine, sorgularınızı nasıl iyileştirebileceğinize odaklanabilirsiniz.
Unutmayın, doğru bir mimari, performanslı sorgular ve verimli kod, sadece uygulamanızın değil, sizin de hayatınızı kolaylaştırır. Hadi, hantallıktan kurtulmuş EF Core sorgularıyla projelerinize hız kazandırın! 🚀
Sonraki görüşmemize kadar kendinize ve sorgularınıza iyi bakın, hoşçakalın :)