Entity Framework Core & Change Tracker API, Tracking vs. No-Tracking Queries, AsNoTracking()
Entity Framework Core 2016 yılından itibaren .NET Core platformu için geliştirilen bir ORM (Object Relational Mapping) aracıdır. Bu araç sayesinde bir çok veritabanı işlemimizi daha kolay bir şekilde yapmaktayız. Kendimizin yapması gereken birçok işi veya bizim halletmemiz gereken Create, Update, Delete ve benzeri işlemleri bize hazır olarak sunmakta.
Bu makalede not defterime bir ara uzun uzun notlar aldığım Entity Framework içerisinde bulunan ChangeTracker yapısından ve izlenilebilen/izlenilemeyen sorgu tiplerinden bahsetmek istiyorum.
Entity Framework Core & ChangeTracker İlişkisi
Entity Framework Core içerisindeki DbContext, aynı DbContext instance’ı kullanılarak alınan her bir varlığın durumunu izlemekten sorumlu olan “Microsoft.EntityFrameworkCore.ChangeTracking” namespace içerisinde bulunan ChangeTracker sınıfını içerir.
Bu sınıfın görevi; DbContext kullanılarak alınan bütün objeleri kapsamının (scope) dışına çıkıncaya kadar izlemektir.
Bunun amacı ise Entity Framework’ün tüm entity ve propertyler üzerinde uygulanan tüm değişiklikleri izleyerek, bu değişimleri veri kaynağına doğru bir şekilde yansıtabilmek ve uygun DML (Data Manipulation Language) ifadeleri oluşturabilmektir.
Tabii ki bu izlenme durumunun standart senaryoda yani default olarak yapıldığını belirtmekte fayda var, ikinci kısımda bunun hakkında konuşacağız.
Buna göre bir entity, bu izlenme süresince herhangi bir zamanda aşağıdaki durumlardan birine sahiptir:
Added, Modified, Deleted, Unchanged, Detached
Haydi bu durumların bizlere ne ifade etmeye çalıştığını kod üzerinde görelim!
DbContext & Migrations
Hızlıca bir DbContext oluşturuyorum;
Developer sınıfı:
DbSet’imi tanımladıktan ve database pathingi verdikten sonra migration işlemlerini Package Manager Console üzerinden gerçekleştiriyorum:
PM> add-migration InitialCreate
PM> update-database
Migration işlemlerinden sonra artık veritabanımız kullanıma hazır. Haydi bir kaç örnek ile beraber nesnelerin durumlarını inceleyelim.
Unchanged State
Nesne, contexte eklendiğinden beri veya SaveChanges() metodunun son çağırılışından itibaren hiçbir değişikliğe uğramamışsa Unchanged durumunu alır. Doğrudan SQL sorgusu veya LINQ-to-Entities sorguları kullanılarak alınan tüm nesnelerde ayrıca bu durumdadır.
Örnek:
Yukarıdaki örnekte görüldüğü gibi FirstOrDefault metoduyla Developers tablosundaki ilk ögeyi almak istedik. Bu işlemden sonra ChangeTracker aracılığıyla işlemin hangi durumda olduğunu sorguluyoruz ve Unchanged olduğunu görüyoruz. Çünkü biz bir okuma işlemi yaptık ve bu veritabanında herhangi bir değişime sebep olmayacak. Dolayısıyla bu okuma işleminden sonra SaveChanges() metodunu çağırmamız biraz anlamsız olacaktır.
Added State
Adından da anlaşılacağı gibi yeni bir nesne ekleme işleminde görülen durumdur. Nesne yenidir ve SaveChanges() metodu çağırılmamıştır. SaveChanges() ile birlikte değişiklikler kaydedildikten sonra nesnenin durumu “Unchanged” olarak değişir.
Örnek:
SaveChanges() metodu ile birlikte değişiklikler db’ye yansıtıldıktan sonra:
Modified State
Nesnenin herhangi bir özelliğinin değeri DbContext kapsamında değiştirilirse, nesnenin durumu Modified olarak değişir. Nesne üzerinde bir değişiklik vardır ve SaveChanges() metodu çağırılmamıştır. Added State gibi SaveChanges() metodundan sonra durumu Unchanged olarak değişir.
Örnek:
SaveChanges() metodu ile birlikte değişiklikler db’ye yansıtıldıktan sonra:
Deleted State
Nesnenin contextten silindiği anda aldığı durumdur. Değişiklikler kaydedildikten sonra nesnenin durumu Detached olarak değişir.
Örnek:
SaveChanges() metodu ile birlikte değişiklikler db’ye yansıtıldıktan sonra:
Detached State
Nesnenin var olduğu ama izlenmediği durumlarda aldığı durumdur. Nesne oluşturulduktan hemen sonra ve contexte eklenmeden önce bu durumdadır. Ayrıca yukarıdaki örnekte gördüğümüz gibi nesne contextten kaldırılırsa yine bu durumu alır.
Örnek:
Gördüğünüz gibi bir nesnenin bağlam içerisindeyken bulunabileceği 5 durumu inceledik. Peki bir nesnenin veya sorgunun izlenmemesini sağlayabilir miyiz?
Tracking vs. No-Tracking Queries
Standart bir senaryoda, default olarak, Entity Framework aracılığıyla bir veritabanından seçilen tüm varlıklar yukarıda anlattığımız gibi izlenir. “İzleme” kelimesinden kastımız, contextimizin izlenen nesneyi gözlemlediği ve üzerinde herhangi bir değişiklik yapılıp yapılmadığını bildiğidir. Bu izleme sonucunda SaveChanges() metodu çalıştığında, nesne üzerinde bir değişiklik varsa veritabanına yansıtılır.
Örnek bir tracking query (izlenen sorgu):
Peki sorgularımızın izlenmesini istemiyorsak?
No-Tracking Queries
Bir önceki “Generic Repository Pattern & ASP.NET Core” yazımda ufaktan değinmiştim aslında. Sonuçların read-only olduğu senaryolarda, daha açmak gerekirse, veritabanında hiçbir değişiklik yaratmayacak ve sadece okuma işlemi yapacağımız senaryolarda aşırı kullanışlı olan sorgu tipidir. Çünkü herhangi bir değişiklik olmadığı için daha hızlı sonuç verecektir. Bundan dolayı veritabanından aldığınız bir veriyi güncellemeniz gerekmiyorsa, izleme gerektirmeyen bir sorgu kullanmanız performans açısından da daha faydalı olacaktır.
AsNoTracking() for No-Tracking
AsNoTracking metodu ile sorguları no-tracking sorgular haline getirebiliyoruz. Yani read-only olan bir sorgu ardından gereksiz bir update işlemini ortadan kaldırıyoruz ve izleme işlemini bu metodun kullanıldığı nesne için kapatıyoruz.
Ayrıca sadece nesne ile sınırlı kalmayıp bütün bir contexti de izlemeye aşağıdaki kod ile kapatabiliriz:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
Bu koddan sonra sorguları normal bir şekilde AsNoTracking metodu eklemeden kullanabilirsiniz.
EF Core, keyless entity tipindeki hiçbir şeyi izlemez.
Eveet bir yazının daha sonuna geldik. Umarım biraz da olsa birilerine katkıda bulunacaktır bu yazı. Kendinize çok iyi ve sağlıklı bakın, kodla kalın! :)