Lazy Loading ve Eager Loading
Entity frameworkün yazılan LINQ sorgularına bağlı olarak veriyi veri tabanından yüklemek için kullandığı 2 farklı Loading yöntemi vardır. Bunlardan biri Lazy loading diğeri ise Eager Loading’dir.
Lazy loading ve eager loading kavramlarını açıklamadan önce üzerinde çalışacağımız tablolardan bahsedelim. Microsoftun bize internetten hazır olarak verdiği AdventureWorks veritabanında bulunan “Person” ve “PersonPhone” isimli iki tane tablomuz var ve bu tablolar direkt olarak BusinessEntityID üzerinden birbirine bağlı. Siz de isterseniz AdventureWorks’ü internetten indirip restore edebilir ya da elle kendi tablolarınızı oluşturabilirsiniz. Aşağıda da tabloların diagramları gözükmekte. Eager loading ve Lazy loading kavramlarını bu iki tablo üzerinden açıklayacağım.
Entityler içerisinde navigasyon property adında bir özellik tanımlanmıştır. Bu özellik her iki entity içinde iki entity arasındaki ilişkiden kaynaklanan bağ ile oluşturulmuş özelliklerdir. Bu sayede herhangi bir “Person” nesnesine ait “PersonPhone” kayıtlarına tek bir özellik üzerinden (PersonNesnesi.PersonPhone) erişebiliriz.
a.) Lazy Loading
Oluşturulan yeni bir entity model için varsayılan olarak tanımlı data yükleme yönetimidir. Bu yöntemde veriler sorguya bağlı olarak çekilir ancak veri setinin içindeki tüm dataları yüklemek yerine verilerin çağrıldıkça otomatik yüklenmesi söz konusudur. Yani bir uygulamada birbirine bağli olarak tanımlanmış componentlerin tamamının bir seferde yüklenmesi yerine istemci tarafından çağırıldığında yüklenmesine olanak veren bir yapıdır.
Örneğimizde, AdventureWorks veritabanındaki Person şemasında yer alan “Person” entity seti üzerinde bir sorgu çalıştırıyoruz. Sorgumuz ilk 10 kaydı bize döndürecek. Daha sonra foreach döngüsü içinde her bir “Person” entitysinin “PersonPhone” entitysi ile ilişkisinden oluşturulan her bir kişinin telefon numaralarını ekrana listeliyor olacağız.
using(AdventureWorks2012Entities context = new AdventureWorks2012Entities()) { var Kisiler= from tel in context.Person.Take(10) select tel; // var Kisiler= context.Person.Take((10)).ToList(); sorgusu da yukarıdakiyle aynı işi görecektir. foreach (var kisi in Kisiler) { Console.WriteLine(kisi.FirstName+" "+kisi.LastName); foreach (var telefon in kisi.PersonPhone) { Console.WriteLine("Tel : " + telefon.PhoneNumber); } } }
SQL profiler’ı açıp bu linQ sorgusunun çalıştırılması sonunda arka planda yürütülen SQL sorgularına bir göz atalım. Aşağıda kırmızı çerçeveye alınmış sorgu “Person” entitylerinin yüklenmesini sağlayan sorgudur.
Kırmızı çerçeveye alınmış sorguya daha yakından baktığımızda linQ sorgusu çalıştırıldığında “Person” tablosundaki kayıtları listeleyen bir select ifadesi çalıştırılıyor. Ancak hala, her bir “Person” entitysine ait “PersonPhone” entitylerini sorgulayacak bir ifade yer almıyor.
yukarıdaki soruguyu alıp sql tarafında çalıştırdığımızda sonucu daha net bir şekilde görüyoruz. Sorgu sonucu gelen veriler tamamen “Person” entitysine ait veriler olup bu entity ile BusinessEntityID üzerinden ilişkili ve kodun ilerleyen satırlarında istenilen “PersonPhone” entitysine ait hiç bir data bulunmamaktadır.
Yukarıda sql profiler’da turuncu ile çerçeve içerisine aldığımız sorgularda toplam 10 adet sorgu bulunuyor ve sql’in sp_execute_sql fonkyonu ile çalıştırılmış. Kodlara tekrar göz attığımızda anlıyoruz ki “Person”entitiysi için “Personphone” entitylerini listelemek için yazdığımız içteki foreach döngüsünde 10 adet “Personphone” entitysi için 10 kez sorgu çalıştırılmış. 10 kez çalıştırılan sorgulardan örnek olması açısından 2 tanesini sql profilerdan alıp sql’de çalıştırdığımızda sonucu daha net göreceğiz.
Bu da bize gösteriyor ki “Person” entitiysi ile ilişkili “PersonPhone” entityleri ilk linq sorgusu çalıştırıldığında değil, daha sonra ihtiyaç duyulduğunda otomatik olarak çağırılıyor.
b.) Eager Loading
Eager loading Linq sorgusu çalıştırıldığında verilerin tamamını yükler ve hafızaya alır.
Aşağıdaki örnekte ilk olarak lazyloading özelliği disable edildi. Bu sayede default olarak tanımlanan lazy loading özelliği pasif hale geliyor. Artık “Person” entitylerini sorgularken buna “Personphone” entitylerini de dahil ediyoruz. Yani entityleri daha sorgu sırasında birbirine bağlayıp tek seferde çekiyoruz.
using (AdventureWorks2012Entities context = new AdventureWorks2012Entities()) { context.Configuration.LazyLoadingEnabled = false; var Kisiler = from tel in context.Person.Include("PersonPhone").Take(10) select tel; // var Kisiler = context.Person.Include("PersonPhone").Take((10)).ToList(); sorgusu da aynı işi görecektir. foreach (var kisi in Kisiler) { Console.WriteLine(kisi.FirstName+" "+ kisi.LastName); foreach (var telefon in kisi.PersonPhone) { Console.WriteLine("Tel : " + telefon.PhoneNumber); } } }
Yukarıdaki kodu çalıştırdığımızda ise sql profiler’a giden sorgu aşağıdaki gibidir. Görüldüğü üzere ilişkili bilgileri ihtiyaç oldukça değil tek bir seferde tek bir sorgu ile çekmiştir.
Yukarıdaki sorguyu sql profilerdan alıp sql tarafında çalıştırdığımızda ise sonucu daha net görmekteyiz.
Lazy Loading ve Eager Loading’in Avantaj/Dezavantajları
Uygulamanın ihtiyaçlarını tam olarak bilmeden hangi loading türünün daha avantajlı olduğunu söyleyemeyiz. İki türün de kendine göre avantajları ve dezavantajları mevcut.
- Yukarıda detaylıca anlatıldığı üzere , lazy loading ile birbiriyle ilişkili olan entityler ihtiyaç oldukça çekilir. Bu da bize içinde bulunduğumuz case’e göre performans açısından yarar sağlayabilir.
- Lazy loading ilişkili entityleri ihtiyaç oldukça çektiğinden yukarıdaki örneklerde de görüldüğü gibi eager loading’e göre veritabanına çok daha fazla kez bağlanır ve sorgu atar bunun da program için bir maliyeti vardır. Eager loading ise tek sorguda gerekli bilgileri elde eder.Örnek üzerinden gitmek gerekirse ;
Bir sayfada verileri listeleyeceğimizi düşünelim ve listeleyeceğimiz sayfada birbiriyle ilişkili entitylerin bilgileri bir arada sunulacak olsun, yani yukarıdaki örneği baz alırsak kişiler (person) listelenirken listede kişilerin telefonu da(personPhone) aynı satırda gözükecek olsun. Bu durumda lazy loading kullanırsak binlerce kayıt için tek tek veritabanına gidip binlerce sorgu atacaktır. Halbuki bu telefon kayıtlarına daha kişi yani person kayıtlarına ihtiyaç duyduğumuz anda ihtiyaç duyuyoruz yani bu durumda ilişkili kayıtları tek seferde çekmek yani eager loading kullanmak daha avantajlı olacaktır.
Ancak eğer birbiriyle ilişkili bilgiler bir arada görünmeyecek kısımlardaysa ve başka sayfadalarsa ilgili noktaya gittiğimizde açılacaklarsa bu durumda bu verileri o an ihtiyacımız olduğunda çekmek daha mantıklıdır.
- Son olarak yukarıdaki örnek için lazy loading ve eager loading arasındaki çalışma hızı farkını değerlendirecek olursak, lazy loadingin tekrar tekrar database’e bağlanması sebebiyle hızı kayıt sayısı arttıkça eager loadingin üzerine çıkıyor.
Lazy Loading Eager Loading
100 kayıt için ortalama 1 sn 1 sn
1000 kayıt için ortalama 2.5 sn 2 sn
5000 kayıt için ortalama 7.5 sn 5 sn
10000 kayıt için ortalama 9.5 sn 6.3 sn
Gayet açık ve anlamlarını karşılaşmış.Teşekkürler…
Yazılarınız gercekten cok acıklayıcı ve net teşekkürler. Takip edebileceğimiz başka platformlar var mı github, medium gibi
Gayet güzel bir anlatım teşekkürler.
Teşekkürler süper anlatım
Gayet faydalı.Ben genelde Eager Loading yapısını kullanıyorum. Sadece ihtiyacın olan tablolardaki bilgileri al kullan…