Отслеживание объектов и AsNoTracking

Последнее обновление: 21.11.2021

Запросы могут быть отслеживаемыми и неотслеживаемыми. По умолчанию все запросы, которые возвращают объекты классов моделей являются отслеживаемыми. Когда контекст данных извлекает данные из базы данных, 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.

Свойство ChangeTracker

Кроме использования метода 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}");
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850