EF Core'da AsNoTracking(), bir sorgunun döndürdüğü varlıkların change tracker'a eklenmemesini sağlar. Varsayılan davranışta varlık döndüren sorgular "tracking" sorgulardır: EF Core her varlığı izler, üzerindeki değişiklikleri SaveChanges sırasında veritabanına yazar ve aynı anahtarlı satır için hep aynı nesneyi döndürür. Sonuçları yalnızca okuyacaksanız no-tracking sorgu genellikle daha hızlıdır, çünkü takip bilgisinin kurulmasına gerek kalmaz.
Entity Framework Core belgeleri bu seçimi basit bir hız ayarı olarak değil, sorgu sonucunun anlamını değiştiren bir karar olarak anlatır: takip, kimlik çözümlemesi (identity resolution) ve navigation fixup birbirine bağlıdır. Bu yazıda tracking ve no-tracking sorguların farkını, AsNoTrackingWithIdentityResolution seçeneğini, takip davranışını context ve uygulama düzeyinde değiştirmeyi, projeksiyonlarda neyin takip edildiğini ve belgelerdeki ölçüm sonuçlarını ele alıyoruz. Change tracker'ın iç yapısı ve EntityState değerleri change tracker ve entity state yazısında; tüm seri için EF Core öğrenme rehberi.
Tracking sorgular: varsayılan davranış

Varlık türü döndüren sorgular varsayılan olarak takip edilir. Bu, varlık üzerinde yaptığınız değişikliğin SaveChanges çağrısında algılanıp veritabanına yazılması demektir. Belgelerdeki örnekte bir blogun puanı değiştirilir ve kaydedilir:
var blog = await context.Blogs.SingleOrDefaultAsync(b => b.BlogId == 1);
blog.Rating = 5;
await context.SaveChangesAsync();Takip eden bir sorgu sonuçları döndürürken EF Core varlığın context'te zaten olup olmadığını kontrol eder. Varsa aynı nesneyi döndürür; bu, no-tracking sorguya göre daha az bellek kullanabilir ve daha hızlı olabilir. Burada önemli bir ayrıntı vardır: EF Core, izlenen varlığın kayıttaki mevcut ve orijinal değerlerinin üzerine veritabanındaki değerleri yazmaz. Varlık context'te yoksa yeni bir nesne oluşturulur ve context'e bağlanır. Ayrıca context'e eklenmiş ama henüz veritabanına kaydedilmemiş varlıklar sorgu sonuçlarında yer almaz.
Bu kuralların pratikteki anlamı şudur: aynı context ile bir blogu yükleyip bir süre sonra aynı blogu tekrar sorgularsanız, veritabanında başka bir işlem o satırı değiştirmiş olsa bile elinizdeki nesne eski değerleri taşır. Kimlik çözümlemesi belgeleri bunu açıkça vurgular: EF Core DbSet üzerindeki LINQ sorgusunu her zaman veritabanında çalıştırır, ama takip eden sorguda dönen varlıklar zaten izleniyorsa veritabanından gelen veriyle yeni nesne oluşturmak yerine izlenen nesneler kullanılır. Belgeler bunu her iş birimi için yeni bir context kullanmanın iyi bir gerekçesi olarak gösterir. İzlenen bir varlığı veritabanındaki son hâliyle yenilemek gerekiyorsa EntityEntry.Reload ya da GetDatabaseValues kullanılabilir.
No-tracking sorgular
No-tracking sorgular sonuçların salt okunur kullanıldığı senaryolar için uygundur ve takip bilgisinin kurulması gerekmediği için genellikle daha hızlı çalışır. Belgelerin önerisi nettir: veritabanından alınan varlıkların güncellenmesi gerekmiyorsa no-tracking sorgu kullanılmalıdır. No-tracking sorgu ayrıca yerel değişiklikleri ve eklenmiş varlıkları dikkate almadan, doğrudan veritabanındaki duruma göre sonuç verir.
var blogs = await context.Blogs
.AsNoTracking()
.ToListAsync();Takip davranışını tek tek sorgularda değil context örneği düzeyinde de değiştirebilirsiniz. ChangeTracker.QueryTrackingBehavior özelliği ayarlandıktan sonra o context örneğindeki sorgular varsayılan olarak no-tracking çalışır:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var blogs = await context.Blogs.ToListAsync();Varsayılanı uygulama düzeyinde değiştirmek
Sorguların çoğunda takip davranışını değiştirdiğinizi fark ederseniz varsayılanı seçenekler üzerinden değiştirmek daha temizdir. UseQueryTrackingBehavior tüm sorguları varsayılan olarak no-tracking yapar; kaydetmek üzere değişiklik yapacağınız belirli sorgularda ise AsTracking() ile takibi yeniden açarsınız.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True;ConnectRetryCount=0")
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
// Değiştirilip kaydedilecek sorgu
var blog = await context.Blogs.AsTracking().SingleAsync(b => b.BlogId == 1);Bu tercih ekip içinde açıkça bilinmelidir. Varsayılan no-tracking olan bir context'te AsTracking() eklemeyi unutmak, değişikliklerin sessizce kaydedilmemesine yol açar: SaveChanges hata vermez, çünkü kaydedilecek bir değişiklik görmez. Seçenekleri nerede yapılandıracağınız DbContext yapılandırma ve yaşam döngüsü yazısında.
Kimlik çözümlemesi ve yinelenen nesneler
Takip eden sorgu change tracker'ı kullandığı için kimlik çözümlemesi yapar: bir varlık zaten izleniyorsa aynı nesne döner; sonuçta aynı varlık birden çok kez geçiyorsa her geçişte aynı nesne kullanılır. No-tracking sorgular ise change tracker kullanmaz, kimlik çözümlemesi yapmaz ve sonuçta aynı varlık birden fazla kez geçtiğinde her seferinde yeni bir nesne döndürür.
Belgelerin performans sayfası bunu somut bir örnekle anlatır: çok sayıda yazıyı her birinin bağlı olduğu blogla birlikte yüklediğinizi ve 100 yazının aynı bloga ait olduğunu varsayın. Tracking sorgu bunu kimlik çözümlemesiyle fark eder ve 100 yazının tamamı aynı tekil blog nesnesini gösterir. No-tracking sorgu ise aynı blogu 100 kez çoğaltır ve uygulama kodunun buna göre yazılması gerekir. Örneğin blog nesnelerini referans eşitliğiyle gruplayan bir kod, no-tracking sonuçta beklediğinizden çok daha fazla grup görür.
AsNoTrackingWithIdentityResolution
İki dünyayı birleştirmek de mümkündür: takip etmeyen ama sonuç içinde kimlik çözümlemesi yapan bir sorgu. Bunun için AsNoTrackingWithIdentityResolution() operatörü ve QueryTrackingBehavior enum'undaki karşılığı vardır. Belgelere göre bu modda sonuçlar üretilirken arka planda bağımsız bir change tracker kullanılır, böylece her nesne yalnızca bir kez oluşturulur. Bu tracker context'inkinden farklı olduğu için sonuçlar context tarafından takip edilmez; sorgu tamamen dolaşıldıktan sonra bu tracker kapsam dışına çıkar ve gerektiğinde çöp toplayıcı tarafından temizlenir.
var blogs = await context.Blogs
.AsNoTrackingWithIdentityResolution()
.ToListAsync();No-tracking sorguların neden varsayılan olarak kimlik çözümlemesi yapmadığını kimlik çözümlemesi belgeleri açıklar: bu, çok sayıda varlığı akış hâlinde döndüren sorgularda performansı etkiler, çünkü kimlik çözümlemesi sonradan yinelenen nesne oluşturmamak için dönen her nesnenin kaydını tutmayı gerektirir. Yinelenen nesneler, sonuçlar serileştirilip istemciye gönderilecekse genellikle sorun değildir.
Projeksiyonlarda ne takip edilir?

Sorgunun sonuç türü bir varlık türü olmasa bile EF Core, sonucun içinde yer alan varlık türlerini varsayılan olarak takip eder. Belgelerdeki kurallar şöyle özetlenebilir:
- Anonim bir türün içinde bir varlık nesnesi döndürüyorsanız, örneğin
new { Blog = b, PostCount = b.Posts.Count() }, sonuçtakiBlognesneleri takip edilir. - LINQ bileşiminden gelen varlıklar da takip edilir;
new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() }sorgusunda hem blog hem de seçilen yazı izlenir. - Sonuç hiçbir varlık nesnesi içermiyorsa, örneğin
new { Id = b.BlogId, b.Url }gibi yalnızca değerler döndürüyorsanız, hiçbir şey takip edilmez. - EF Core en üst düzey projeksiyonda istemci tarafı değerlendirmeyi destekler. İstemci metoduna bir varlık nesnesi verdiğiniz için EF Core o varlığı oluşturmak zorunda kalırsa, o nesne de takip edilir.
- Anahtarı olmayan (keyless) varlık türleri hiçbir zaman takip edilmez; anahtarı olan diğer tüm varlık türleri yukarıdaki kurallara göre izlenir.
Dördüncü maddedeki durum belgelerde bir blogun adresini düzenleyen StandardizeUrl(blog) metoduyla gösterilir: projeksiyon yalnızca Id ve düzenlenmiş adresi döndürse de metoda tüm blog nesnesi verildiği için bloglar takip edilir. Metoda yalnızca blog.Url değerini verseydiniz varlık nesnesi oluşmaz ve takip gerekmezdi. İstemci tarafı değerlendirmenin sınırları client evaluation hatası yazısında, keyless türler ve ham SQL ise FromSql ile ham SQL yazısında.
Performans: belgelerdeki ölçüm
Belgelerin performans sayfası takip maliyetinin nereden geldiğini iki maddeyle açıklar. Birincisi, EF içeride izlenen nesnelerin bir sözlüğünü tutar; yeni veri yüklendiğinde o anahtar için zaten izlenen bir nesne var mı diye bu sözlüğe bakar ve sözlüğün bakımı ile aramalar sonuçların yüklenmesi sırasında zaman alır. İkincisi, EF yüklenen nesneyi uygulamaya vermeden önce onun bir anlık görüntüsünü (snapshot) alır ve SaveChanges sırasında değişiklikleri bulmak için nesneyi bu görüntüyle karşılaştırır; görüntü ek bellek tutar ve görüntü alma işlemi de zaman alır.
Aynı sayfada, her biri 20 yazı içeren 10 blogu yükleyen bir sorgu için tracking ve no-tracking karşılaştırmasının sonuçları paylaşılır. Belgelerdeki bu ölçüme göre AsTracking ortalama 1.414,7 mikrosaniye sürüp 380,11 KB bellek ayırırken AsNoTracking ortalama 993,3 mikrosaniye sürmüş ve 232,89 KB ayırmıştır; oran olarak no-tracking sürümü tracking sürümünün yaklaşık 0,71 katı süre almıştır. Bu rakamlar belgelerin kendi ortamında ölçülmüştür; kendi uygulamanızda veri modeline ve sorgulara göre farklı sonuç alırsınız ve belgeler de ölçüm kodunu kendi ölçümleriniz için temel olarak kullanmanızı önerir.
| Davranış | Tracking | AsNoTracking | AsNoTrackingWithIdentityResolution |
|---|---|---|---|
| SaveChanges değişiklikleri görür | Evet | Hayır | Hayır |
| Kimlik çözümlemesi | Evet (context genelinde) | Hayır | Evet (yalnızca bu sorgu içinde) |
| Zaten izlenen nesne | O nesne döner | Yeni nesne | Yeni nesne |
| Tipik kullanım | Değiştirip kaydetme | Salt okunur listeler | Salt okunur ama tekrarsız grafik |
No-tracking sonra Attach: neden önerilmez?
Bazı ekipler "her şeyi no-tracking sorgula, güncelleme gerekince Attach et" yaklaşımını dener. Kimlik çözümlemesi belgeleri bunu rutin bir alışkanlık hâline getirmemeyi önerir: no-tracking sorgu yapıp dönen varlıkları aynı context'e bağlamak, tracking sorgu kullanmaktan hem daha yavaş hem de doğru yapması daha zordur. Performans sayfası da takip maliyetinden kaçınarak güncelleme yapmanın mümkün olduğunu, ama bunun takip yükünü EF'ten size aktardığını ve ancak profil ya da ölçümle takip maliyetinin kabul edilemez olduğu gösterildiğinde denenmesi gerektiğini söyler. Aynı anahtarlı ikinci bir nesneyi bağlamaya çalıştığınızda karşılaşılan "already being tracked" hatası ve çözümleri disconnected entity: Attach ve Update yazısında.
Bu konuyla bağlantılı bir ayrıntı daha: EF Core 8 sürüm notlarına göre lazy loading, no-tracking sorgularla dönen varlıklarda da çalışır; bu varlıklar takip edilmese de context'lerine bir referans tutar. Uzun yaşayan nesnelerde bu bir bellek sızıntısı kaynağı olabilir. Ayrıntılar lazy loading yazısında.
Mini örnek: liste ekranı ve düzenleme ekranı
Tipik bir yönetim uygulamasında iki farklı ihtiyaç vardır. Liste ekranı blogları yalnızca gösterir; düzenleme ekranı ise tek bir blogu yükleyip değiştirir. Aşağıdaki kod bu ikisini ayrı ayrı ele alır. Kod belgelerdeki kalıplardan uyarlanmıştır ve çalıştırılmamıştır.
// Liste: salt okunur, takip gerekmez
var liste = await context.Blogs
.AsNoTracking()
.OrderBy(b => b.BlogId)
.Take(50)
.ToListAsync();
// Düzenleme: takip gerekir, değişiklik kaydedilir
var blog = await context.Blogs.SingleAsync(b => b.BlogId == blogId);
blog.Rating = yeniPuan;
await context.SaveChangesAsync();Liste sorgusu change tracker'a hiçbir şey eklemez, bu yüzden aynı context'te daha sonra çalışan düzenleme sorgusu bloğu veritabanından taze bir nesne olarak alır ve izlemeye başlar. Liste sorgusu tracking olsaydı, düzenleme sorgusu veritabanına yine gidecek ama listeden kalan, belki artık eski olan nesneyi döndürecekti. Web uygulamalarında context genellikle istek başına oluşturulduğu için bu iki işlem çoğu zaman zaten ayrı context'lerde olur; yine de aynı istekte listeleme ve güncelleme yapan kodda bu ayrım önem kazanır. Kaydetme tarafının ayrıntıları SaveChanges ile ekleme, güncelleme ve silme yazısında.
Sık yapılan hatalar

- Salt okunur sorgularda takibi açık bırakmak: Gereksiz bellek ve işlem maliyetidir. Güncellenmeyecek veriler için
AsNoTrackingkullanın. - No-tracking sonucu değiştirip kaydetmeyi beklemek: Takip edilmeyen nesnedeki değişiklikleri
SaveChangesgörmez. - Global no-tracking ayarında AsTracking'i unutmak: Güncellemeler sessizce kaybolur.
- Uzun yaşayan context'te güncel veri beklemek: Tracking sorgu izlenen nesneyi döndürür; veritabanındaki yeni değerleri görmek için yeni context,
Reloadya da no-tracking sorgu kullanın. - No-tracking sonuçta referans eşitliğine güvenmek: Aynı satır farklı nesneler olarak gelebilir; gerekiyorsa
AsNoTrackingWithIdentityResolutionkullanın. - Filtrelenmiş include'u tracking context'te yorumlamak: Daha önce yüklenmiş varlıklar sonuca karışabilir; ayrıntı Include ile eager loading yazısında.
Sık Sorulan Sorular
AsNoTracking her zaman daha mı hızlıdır?
Belgeler no-tracking sorguların genellikle daha hızlı olduğunu söyler, ama tracking sorgunun context'te zaten bulunan bir varlık için aynı nesneyi döndürmesinin daha az bellek kullanabileceğini ve daha hızlı olabileceğini de belirtir. Ayrıca no-tracking sorgu aynı satırı birden çok kez çoğaltabilir. Kendi senaryonuzda ölçmek en doğrusudur.
Projeksiyonla dönen anonim türler takip edilir mi?
Anonim türün kendisi takip edilmez; ama içinde bir varlık nesnesi varsa o varlık takip edilir. Yalnızca skaler değerler döndüren bir projeksiyon hiçbir şeyi takip etmez.
FromSql ile dönen varlıklar takip edilir mi?
Evet. Belgelere göre FromSql ve FromSqlRaw kullanan sorgular diğer LINQ sorgularıyla tamamen aynı takip kurallarına uyar; varlık türü döndürüyorlarsa sonuçlar varsayılan olarak takip edilir ve AsNoTracking eklenebilir.
Context düzeyindeki QueryTrackingBehavior ile UseQueryTrackingBehavior farkı nedir?
context.ChangeTracker.QueryTrackingBehavior yalnızca o context örneğini etkiler. UseQueryTrackingBehavior ise seçeneklerde ayarlandığı için bu seçeneklerle oluşturulan tüm context örneklerinin varsayılanını belirler. İki durumda da tek tek sorgularda AsTracking ya da AsNoTracking ile varsayılan geçersiz kılınabilir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: EF Core öğrenme rehberi · Change tracker ve entity state · Disconnected entity: Attach ve Update · SaveChanges ile kaydetme · EF Core performans optimizasyonu