Lazy loading

Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7

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

Lazy loading или ленивая загрузка предполагает неявную автоматическую загрузку связанных данных при обращении к навигационному свойству. Однако здесь есть ряд условий:

  • При конфигурации контекста данных вызвать метод UseLazyLoadingProxies()

  • Все навигационные свойства должны быть определены как виртуальные (то есть с модификатором virtual), при этом сами классы моделей должны быть открыты для наследования

Используем lazy loading. Прежде всего добавим в проект через nuget пакет Microsoft.EntityFrameworkCore.Proxies.

Lazy loading в Entity Framework Core

Далее определим следующие модели и контекст данных:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace HelloApp
{
    public class ApplicationContext : DbContext
    {
        public DbSet<Company> Companies { get; set; }
        public DbSet<User> Users { get; set; }
		
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseLazyLoadingProxies()
                .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;");
        }
    }

    public class Company
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public virtual List<User> Users { get; set; } = new List<User>();
    }

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public int CompanyId { get; set; }
        public virtual Company Company { get; set; }
    }
}

Прежде всего в методе OnConfiguring у объекта DbContextOptionsBuilder вызывается метод UseLazyLoadingProxies(), который делает доступной ленивую загрузку.

Также навигационное свойство Users в классе Company и навигационное свойство Company в классе User определены как виртуальные.

После этого мы можем загрузить пользователей и связанные с ними компании следующим образом:

using (ApplicationContext db = new ApplicationContext())
{
    // пересоздадим базу данных
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
 
    // добавляем начальные данные
    Company microsoft = new Company { Name = "Microsoft" };
    Company google = new Company { Name = "Google"};
    db.Companies.AddRange(microsoft, google);
 
 
    User tom = new User { Name = "Tom", Company = microsoft };
    User bob = new User { Name = "Bob", Company = google };
    User alice = new User { Name = "Alice", Company = microsoft };
    User kate = new User { Name = "Kate", Company = google };
    db.Users.AddRange(tom, bob, alice, kate);
 
    db.SaveChanges();
}
using (ApplicationContext db = new ApplicationContext())
{
    var users = db.Users.ToList();
    foreach (User user in users)
        Console.WriteLine($"{user.Name} - {user.Company?.Name}");
}

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

Tom - Microsoft
Alice - Microsoft
Bob - Google
Kate - Google

Теперь посмотрим, что происходит на уровне базы данныx. Вначале получаем пользователей:

var users = db.Users.ToList();

В базе данных выполняется sql-команда:

SELECT [u].[Id], [u].[CompanyId], [u].[Name]
FROM [Users] AS [u]

Далее в цикле перебираем всех полученных пользователей и обращаемся к навигационному свойству Company для получения связанной компании:

foreach (User user in users)
	Console.WriteLine($"{user.Name} - {user.Company?.Name}");

Поскольку идет обращение к навигационному свойству Company, то автоматически подтягиваются связанные с ним данные - объекты Company. В данном случае у нас выше было добавлено только 2 компании и обе эти компании ссвязанные с перебираемыми пользователями: два пользователя связаны с одной компанией, а два других пользователя - с другой. Поэтому будут выполняться два запроса:

SELECT [c].[Id], [c].[Name]
FROM [Companies] AS [c]
WHERE [c].[Id] = @__p_0

@__p_0 в данном случае это параметр, который хранит значения свойства CompanyId пользователя, для которого надо получить компанию.

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

То есть если совместить консольный вывод и выполняемые выражения SQL, то получится следующим образом:

// получение всех пользователей
SELECT [u].[Id], [u].[CompanyId], [u].[Name]
FROM [Users] AS [u
// идет обращение к свойству Company, его компании нет в контексте
// поэтому выполняется sql-запрос
SELECT [c].[Id], [c].[Name]
FROM [Companies] AS [c]
WHERE [c].[Id] = @__p_0
Tom - Microsoft
// компания этого пользователя уже в контексте, не надо выполнять новый запрос
Alice - Microsoft
// компании следующего пользователя нет в контексте
// поэтому выполняется sql-запрос
SELECT [c].[Id], [c].[Name]
FROM [Companies] AS [c]
WHERE [c].[Id] = @__p_0
Bob - Google
// компания этого пользователя уже в контексте, не надо выполнять новый запрос
Kate - Google

Таким же образом можно загрузить компании и связанных с ними пользователей:

using (ApplicationContext db = new ApplicationContext())
{
	var companies = db.Companies.ToList();
	foreach (Company company in companies)
	{
		Console.Write($"{company.Name}:");
		foreach(User user in company.Users)
			Console.Write($"{user.Name} ");
		Console.WriteLine();
	}
}

Однако при использовании lazy loading следует учитывать что если в базе данных произошли какие-нибудь изменения, например, другой пользователь изменил данные, то данные не перезагужаются, контекст продолжает использовать те данные, которые были ранее загружены, как собственно было показано выше.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850