Bilog v1.3

Loading

Powered By Yavuz MERCAN

Entity Framework İçin Performans İpuçları - Yavuz MERCAN

Entity Framework İçin Performans İpuçları

Entity Framework İçin Performans İpuçları
Yavuz MERCAN - 11.11.2019

Merhaba arkadaşlar,

Bu yazımda Entity Framework’ü daha performanslı kullanmaya yönelik ipuçları paylaşacağım.

Tabi Entity Framework’e gelmeden önce Database tarafını özellikle tablo yapısını, index yapısını vs. iyi kurgulamak gerekir.Database yapınızı ne kadar iyi kurgularsanız o derece performans elde etmiş olursunuz.

Lafı fazla uzatmadan başlamak istiyorum:

Lazy Loading

EF’te lazy loading’i pasif hale getirmemiz gerekmektedir.Çünkü EF’te herhangi bir tablo çektiğinizde, o tabloyla ilişkili bütün tablolara ait kayıtlar da her seferinde beraberinde getirilir.Lazy loading olarak getirilir yani ulaşmak isterseniz ulaşırsınız, çağırmazsanız ulaşamazsınız ama Profiler’dan oluşan koda baktığınızda her seferinde ihtiyacınız olmayan datalar sorgunuza dahil edilmiş olur.Bu da performans kaybına yol açacaktır.Bu sebeple aşağıdaki ayarı yapıyoruz.

public NorthwindEntities()
  : base("name=NorthwindEntities")
  {
  this.Configuration.LazyLoadingEnabled = false;
  }

 

AsNoTracking() Kullanmak

Öncelikle EF ile herhangi bir tablo çektiğinizde, bu tablonun bir kopyası da beraberinde getirilir.Bunun sebebi, EF kayıt üzerinde herhangi bir değişiklik olup olmadığını o kopya kayıt ile karşılaştırarak bulur.

Eğer çekeceğiniz data üzerinde herhangi bir değişiklik yapmayacaksanız, örneğin sadece listeleme amaçlı çekiyor iseniz, AsNoTracking() fonksiyonundan faydalanabilirsiniz.

public ICollection<City> Get()
  {
  using (var ctx = new NorthwindEntities())
  {
  return ctx.Cities.AsNoTracking().ToList();
  }
  }

Select Kullanmak

Herhangi bir data çekerken bu dataya ait bütün kolonları değil,sadece ihtiyacınız olan kolonları çekerseniz performans sağlamış olursunuz.

using (var ctx = new NorthwindEntities())
  {
  var data = ctx.Notices.AsNoTracking().OrderByDescending(o=> o.CreateDate).
  Select(q=> new
  {
  q.Detail,
  q.Description,
  q.Image
  }).ToList();
   
  }

Döngüler

Mümkün olduğunca döngü içerisinde database işlemlerini yapmamaya gayret etmek gerek.Örneğin, bir döngü içerisinde database deki bir dataya ulaşmamız gerekiyor ise, döngüye girmeden ilgili datayı çekip daha sonra, çektiğimiz data üzerinden sorgulama yapmalıyız.Döngüdeki dönüş sayısı boyunca database’e gidip geldiğini düşünürsek büyük bir performans kaybı olacaktır.

foreach (var city in cityList)
  {
  var districts = ctx.Districts.AsNoTracking().Where(q => q.CityId = city.Id).ToList();
  }

Bu şekilde bir kullanım yerine döngüye henüz girmeden bütün District datalarını çekerek döngüdeki işlemlerde, belleğe aldığınız o listeyi kullanırsanız performans sağlamış olursunuz.

Contains yerine Join

EF’te contains kullanımı hiçbi zaman tavsiye edilmez.Sebebi ise contains metodunun sql tarafında WHERE IN olarak çevrilmesidir.Özellikle benimde bizzat deneyimlediğim bir olaydır.Contains metodu ile içerisinde bin küsür id değerinin olduğu bir listeyi sorgulamam gerekti.Önce aşağıdaki ilk kodda olduğu gibi bir yöntemle denedim ve çok hantal çalıştığını gördüm.

Contains yerine ne kullanabilirim diye araştırırken join kullanımının önerildiğini görünce aşağıdaki ikinci kodda olduğu gibi kodu düzenledim ve büyük bir performans artışı elde ettim.

List<int> categoryList = new List<int> { 1, 3, 4, 6, 7 };
  var products = _db.Products.Where(q => categoryList.Contains(q.CategoryID)).ToList();
   
  yerine
   
  var productsWithJoin = _db.Products.Join(categoryList,
  p => p.CategoryID,
  c => c,
  (p, c) => new { ProductName = p.ProductName, CategoryId = c }).ToList();

Toplu Insert İşlemlerinde AddRange Kullanmak

for (int i = 0; i < 100; i++)
  {
  var product = new Product();
  _db.Products.Add(product);
  }
   
  _db.SaveChanges();
   
  yerine
   
  List<Product> products = new List<Product>();
  for (int i = 0; i < 100; i++)
  {
  var product = new Product();
  products.Add(product);
  }
   
  _db.Products.AddRange(products);
  _db.SaveChanges();

AutoDetectChangesEnabled Özelliği

Bu özellik default olarak açık bulunmaktadır.Görevi; DbSet’in Add, Attach, Find, Local, Remove metotlarını çağırırken aynı zamanda DbContext’in GetValidationErrors, Entry, SaveChanges metotlarını da çağırır.Nesnelerde, veri tabanına gitmeden önce herhangi bir değişim olup olmadığını kontrol eder.Bu da çok sayıda kaydın insert edilmeye çalışıldığını düşünürsek hayli yüklü bir performans kaybına neden olacaktır.Özellikle bulk insert gibi işlemlerde.

Bu özelliği kapatmak için;

this.Configuration.AutoDetectChangesEnabled = false;

ayarını yapmamız yeterlidir.

Skip Take Kullanmak

Bir sayfada belli sayıda data gösterilecekse veritabanında bütün dataları çekmenin bir manası olmayacaktır.Bu sebeple Skip ve Take fonksiyonları kullanılabilir.

Aşağıdaki örnekte, Skip metodu ile ilk 10 kayıt es geçilip, Take metodu ile sonrasında gelen 10 kayıt listelenmiştir.

var data = ctx.Notices.AsNoTracking().Skip(10).Take(10).ToList();

 

Disposing

Veritabanı işlemleri kullandığınız Db Context’i kullandıktan sonra dispose etmeniz faydalı olacaktır.

using() içerisinde kullanırsanız, bu kod bloğundan çıktığında otomatik olarak dispose edilecektir.Aynı zamanda IDisposable bir nesne olduğu için, işlemleriniz bittiğinde Dispose metodunu da kullanabilirsiniz.

Tek Data Çekimi

Eğer belli bir kritere göre tek bir data çekecek olursak bunu şu şekilde gerçekleştirmeliyiz.Sıklıkla gördüğüm yanlışlardan biridir.

var data = ctx.Notices.AsNoTracking().Where(q => q.NoticeId == id).ToList().FirstOrDefault();
   
  yerine
   
  var data = ctx.Notices.AsNoTracking().FirstOrDefault(q => q.NoticeId == id);


 

 

 

SaveChanges() Kullanmak

Bir metotta veritabanı işlemlerinizi tümüyle hallettikten sonra en son SaveChanges metodunu çağırın.Aralarda çağırılan bu metotlar gereksiz maliyete sebep olur.

using (var ctx = new AbcEntities())
  {
  var gallerylist = ctx.NoticeGalleries.AsNoTracking().Where(q => q.NoticeId == id).ToList();
  foreach (var gallery in gallerylist)
  {
  ctx.Entry(gallery).State = System.Data.Entity.EntityState.Deleted;
  }
   
  var item = ctx.Notices.AsNoTracking().FirstOrDefault(q => q.NoticeId == id);
  ctx.Entry(item).State = System.Data.Entity.EntityState.Deleted;
   
  return ctx.SaveChanges() > 0;
  }

ToList() Metodunu Etkin Kullanmak

EF’te yazmış olduğunuz sorgu ToList() metodunu çağırmadıkça database’e gitmeyecek yani çalışmayacaktır.ToList ve SaveChanges metotları database bağlantısını tetikleyen metotlardır.

Bu sebeple bir sorgu hazırlarken yazılacak olan bütün koşulları yazıp en son çıktıyı gönderme aşamasına gelindiğinde ToList() metodu ile data çekilebilir.Bu sayede database’e bir kez gitmiş olur ve performans elde ederiz.

var data = ctx.Notices.AsQueryable();
  data = data.Where(q => q.Title.Contains("deneme"));
   
  if (isIncludeGalleries)
  {
  data = data.Include(x => x.NoticeGalleries);
  }
   
  return data.ToList();

String Data Sorguları

.Net tarafında string tipindeki tüm datalar, sql tarafına nvarchar olarak iletilir.Bunun sebebi .Net’in unicode çalışmasıdır.Aşağıdaki gibi bir kod yazdığınızda, profiler da Description alanının nvarchar’a convert edildiğini görürsünüz.Halbuki Description alanı varchar olarak tanımlanmışta olabilir.Bu sebeple eğer nvarchar harici bir alanınız varsa bunu .net tarafında işaretlemeniz gerekmektedir.

var data = ctx.Notices.Where(q => q.Description == "deneme").ToList();

Şöyle ki;

[Column(TypeName = "varchar")]
  public string Description { get; set; }

Index Kullanmak

Entity Framework 6 ile gelen [Index] attribute’ünü kullanarak index oluşturabilir, fazlaca sorguladığınız alanlara index ekleyerek,sorgularınızı daha hızlı hale getirebilirsiniz.

 

[Index( "INDEX_REGNUM", IsClustered=true, IsUnique=true )]
  public int RegistrationNumber { get; set; }

Async Metotları Kullanmak

EF’te .ToListAsync, SaveChangesAsync gibi asenkron çalışan metotlar mevcut.Tabi bu tip metotları kullanmadan önce asenkron mantığını kavramak gerekir.Kullanıcının veya sistemin tetiklediği olayın, sonucuna hemen ihtiyacı varsa asenkron kullanmak mantıksız olur.İhtiyaç dahilinde kullanılabilecek güzel bir yöntemdir.

Cachleme İşlemi

Herşeyi EF’ten beklememek gerek.Çok sık kullanılan ama sürekli değişmeyen(datasal olarak) tabloların veya dataların cache lenmesi her zaman yararınıza olacaktır.Örnek verecek olursam, çoklu dilli bir uygulamada sabit kelimelerin(Anasayfa İletişim Hakkımızda gibi) çevirilerinin tutulduğu, sıkça değişmeyecek ama her zaman ihtiyaç olacak tabloları cachelemeyi düşünebiliriz.

Gereksiz EF Sorgulamaları

EF, database ile iletişime geçtiği her olayda, database’in versiyonunu sorgulamak için ekstra bir sorgu oluşturur.Database versiyonunuz sık sık değişmiyor ise ki sık sık değişmesi durumunu henüz hiç yaşamadım, bu ekstra ve gereksiz sorguyu iptal edebiliriz.Bunun için şöyle bir koda ihtiyacımız var;

public class CustomDbConfiguration : DbConfiguration
  {
  public CustomDbConfiguration()
  {
  SetManifestTokenResolver(new CustomManifestTokenResolver());
  }
  }
   
  public class CustomManifestTokenResolver : IManifestTokenResolver
  {
  public string ResolveManifestToken(DbConnection connection)
  {
  return "2017"; //kullandığınız db versiyonu.
  }
  }

Ve son olarak Entity Framework Her Sorunun Çözümü Değildir

Fazla karmaşık uygulamalar ve raporlamalar için bazen eskiye dönüp ADO.NET kullanmanız, store procedure yazmanız gerekebilir.EF fazla karmaşık işlemlerde sizi ve sisteminizi zorlayabilir.Zaman zaman micro orm’lerden destek almak gerekir.(Özellikle Dapper öneriyorum).Bu sebeple uygulamanın ihtiyacına göre kullanılması ve mümkün olduğunca optimize edilmesi gerekmektedir.

Umarım faydalı bir yazı olmuştur.Herkese mutlu ve performanslı kodlamalar !

 

 

 

Yazıyı Paylaşın

Yorum Yapınız

E-mail adresi yazılmayan yorumlar onaylanmamaktadır.*

add
close

Son Twitlerim

Son Eklenen Makalelerim

Etiketler

Beni Takip Edin