Запросы могут быть отслеживаемыми и неотслеживаемыми. По умолчанию все запросы, которые возвращают объекты классов моделей являются отслеживаемыми. Когда контекст данных извлекает данные из базы данных, Entity Framework Core помещает извлеченные объекты в кэш и отслеживает изменения, которые происходят с этими объектами вплоть до использования метода SaveChanges()/SaveChangesAsync(), который фиксирует все изменения в базе данных. Но нам не всегда необходимо отслеживать изменения. Например, нам надо просто вывести данные для просмотра.
Чтобы данные не помещались в кэш, применяется метод AsNoTracking(). Этот метод применяется к объекту IQueryable. При его применении возвращаемые из запроса данные не кэшируются. То есть запрос является неотслеживаемым. А это означает, что Entity Framework Core не производит какую-то дополнительную обработку и не выделяет дополнительное место для хранения извлеченных из БД объектов. И поэтому такие запросы работают быстрее.
Небольшой пример. Допустим, у нас есть следующие модели и контекст данных:
using Microsoft.EntityFrameworkCore; public class User { public int Id { get; set; } public string? Name { get; set; } public int Age { get; set; } } public class ApplicationContext : DbContext { public DbSet<User> Users { get; set; } = null!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=helloapp.db"); } }
Пусть в базе данных есть есть несколько объектов User:
using (ApplicationContext db = new ApplicationContext()) { // пересоздаем базу данных db.Database.EnsureDeleted(); db.Database.EnsureCreated(); User tom = new User { Name = "Tom", Age = 36}; User bob = new User { Name = "Bob", Age = 39 }; User alice = new User { Name = "Alice", Age = 28 }; User kate = new User { Name = "Kate", Age = 25 }; db.Users.AddRange(tom, bob, alice, kate); db.SaveChanges(); }
При обычном выполнении:
using (ApplicationContext db = new ApplicationContext()) { var user = db.Users.FirstOrDefault(); if (user != null) { user.Age = 18; db.SaveChanges(); } var users = db.Users.ToList(); foreach (var u in users) Console.WriteLine($"{u.Name} ({u.Age})"); }
Консольный вывод:
Tom (18) Bob (39) Alice (28) Kate (25)
Мы видим, что в наборе users первый элемент имеет у свойства Age значение 18.
Причем в данном случае мы можем и не вызывать метод SaveChanges, элемент уже и так будет закэширован. Метод SaveChanges необходим, чтобы применить все изменения с объектами в базе данных.
Но если бы мы использовали метод AsNoTracking(), то результат был бы другой:
using (ApplicationContext db = new ApplicationContext()) { var user = db.Users.AsNoTracking().FirstOrDefault(); if (user != null) { user.Age = 22; db.SaveChanges(); } var users = db.Users.AsNoTracking().ToList(); foreach (var u in users) Console.WriteLine($"{u.Name} ({u.Age})"); }
Консольный вывод будет тот же, что и в предыдущем случае:
Tom (18) Bob (39) Alice (28) Kate (25)
Так как при получении первого элемента используется AsNoTracking, он не будет отслеживаться, и поэтому вызов db.SaveChanges() никак не повлияет на базу данных, а первый элемент сохранит свое первоначальное значение у свойства Age.
Кроме использования метода AsNoTracking, можно отключить отслеживание в целом для объекта контекста. Для этого надо установить значение QueryTrackingBehavior.NoTracking для свойства db.ChangeTracker.QueryTrackingBehavior:
using (ApplicationContext db = new ApplicationContext()) { db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var user = db.Users.FirstOrDefault(); if (user != null) { user.Age = 8; db.SaveChanges(); } var users = db.Users.ToList(); foreach (var u in users) Console.WriteLine($"{u.Name} ({u.Age})"); }
Также можно отключить отслеживание на уровне всего контекста данных, например, в его конструкторе
public ApplicationContext() { ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; }
Вобще через свойство ChangeTracker мы можем управлять отслеживанием объектом и получать разнообразную информацию. Например, мы можем узнать, сколько объектов отслеживается в текущий момент:
using (ApplicationContext db = new ApplicationContext()) { var users = db.Users.ToList(); foreach (var u in users) Console.WriteLine($"{u.Name} ({u.Age})"); int count = db.ChangeTracker.Entries().Count(); Console.WriteLine($"{count}"); }