Выполнение запросов

  • Михаил
  • 12 мин. на прочтение
  • 107
  • 15 Nov 2022
  • 15 Nov 2022

Рассмотрим, как EF обрабатывает запросы на получение данных из БД. Вначале выражения LINQ обрабатываются Entity Framework Core, и на их основе создается объект запроса в той форме, в которой он может обрабатываться провайдером базы данных. Созданный объект запроса кэшируется, что позволяет не пересоздавать его при повторном его выполнении.

Затем этот объект запроса передается провайдеру базы данных, который транслирует его на язык, понятный для базы данных (например, SQL). База данных обрабатывает запрос и возвращает определенный результат.

EF Core получает результат обработки, и дальше его действия зависят от того, отслеживаются ли результаты запроса или нет.

Если запрос является отслеживаемым, то здесь есть два альтернативных варианта:

Если данные, полученные из БД, представляют объекты, которые уже отслеживаются, то есть они уже есть в контексте, то EF возвращает те объекты, которые уже есть в контексте.

Если данные, полученные из БД, представляют объекты, которые еще не отслеживаться, их нет в контексте, то EF создает по этим данным новые объекты, добавляет в контекст, начинает их отслеживать и возвращает их пользователю.

Если запрос является не отслеживаемым, то есть отслеживание отключено с помощью метода AsNoTracking() или установки свойства ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking, то EF создает по этим данным новые объекты и возвращает их пользователю. В отличие от отслеживаемых запросов созданные объекты не добавляются в контекст и не отслеживаются.

Рассмотрим несколько примеров. Для этого возьмем следующую модель User и контекст данных:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public ApplicationContext()
    {
        Database.EnsureDeleted();
        Database.EnsureCreated();
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // добавляем один объект
        modelBuilder.Entity<User>().HasData(new User { Id=1, Name="Tom"});
    }
}

Отслеживаемые запросы:

using (ApplicationContext db = new ApplicationContext())
{
    var user1 = db.Users.FirstOrDefault();
    var user2 = db.Users.FirstOrDefault();
    Console.WriteLine($"Before User1: {user1.Name}   User2: {user2.Name}");
     
    user1.Name = "Bob";
    Console.WriteLine($"After User1: {user1.Name}   User2: {user2.Name}");
}

Консольный вывод:

Before User1: Tom   User2: Tom After User1: Bob   User2: Bob

Так как запрос db.Users.FirstOrDefault() является отслеживаемым, то при получении данных, по ним будет создаваться объект user1, который добавляется в контекст и начинает отслеживаться.

Далее повторно вызывается данный запрос для получения объекта user2. Этот запрос то же является отслеживаемым, поэтому EF увидит, что такой объект уже есть в контексте, он уже отслеживается, и возвратит ссылку на тот же объект. Поэтому все изменения с переменной user1 затронут и переменную user2.

Рассмотрим другой пример:

using (ApplicationContext db = new ApplicationContext())
{
    var user1 = db.Users.FirstOrDefault();
    var user2 = db.Users.AsNoTracking().FirstOrDefault();
    Console.WriteLine($"Before User1: {user1.Name}   User2: {user2.Name}");
     
    user1.Name = "Sam";
    Console.WriteLine($"After User1: {user1.Name}   User2: {user2.Name}");
}

Консольный вывод:

Before User1: Tom   User2: Tom After User1: Sam   User2: Tom

С первым объектом user1 все по прежнему: он также попадает в контекст и отслеживается. А вот второй запрос теперь является не отслеживаемым, так как использует метод AsNoTracking. Поэтому для данных, полученных в результате этого запроса, будет создаваться новый объект, который никак не будет связан с объектом user1. И изменения одного из этих объектов никак не повлияют на второй объект.