Explicit loading

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

Стратегия Explicit loading предполагает явную загрузку данных с помощью метода Load(). Допустим, у нас имеются следующие сущности и контекст данных:

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!;
    public DbSet<Company> Companies { get; set; } = null!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}
public class Company
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public List<User> Users { get; set; } = new ();
}
public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }

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

Загрузим данные по первой компании:

using Microsoft.EntityFrameworkCore;

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())
{
    Company? company = db.Companies.FirstOrDefault();
    if (company != null)
    {
        db.Users.Where(u => u.CompanyId == company.Id).Load();

        Console.WriteLine($"Company: {company.Name}");
        foreach (var u in company.Users)
            Console.WriteLine($"User: {u.Name}");
    }
}

Выражение db.Users.Where(p=>p.CompanyId==company.Id).Load() загружает пользователей в контекст. Подвыражение Where(p=>p.CompanyId==company.Id) означает, что загружаются только те пользователи, у которых свойство CompanyId соответствует свойству Id ранее полученной компании. После этого нам не надо подгружать связанные данные, так как они уже есть в контексте.

Консольный вывод программы:

Company: Microsoft
User: Tom
User: Alice

Важно, что здесь подгружаются только те пользователи, которые непосредственно связаны с компанией. Если нам надо загрузить в контекст вообще все объекты из таблицы Users, то можно было бы использовать следующее выражение db.Users.Load()

Для загрузки связанных данных мы также можем использовать методы Collection() и Reference. Метод Collection применяется, если навигационное свойство представляет коллекцию:

using (ApplicationContext db = new ApplicationContext())
{
    Company? company = db.Companies.FirstOrDefault();
    if(company != null)
    {
        db.Entry(company).Collection(c => c.Users).Load();

        Console.WriteLine($"Company: {company.Name}");
        foreach (var u in company.Users)
            Console.WriteLine($"User: {u.Name}");
    }           
}

На уровне базы данных вызов db.Entry(company).Collection(t=>t.Users).Load() будет трансформироваться в следующую команду SQL:

SELECT "u"."Id", "u"."CompanyId", "u"."Name"
FROM "Users" AS "u"
WHERE "u"."CompanyId" = @__p_0

@__p_0 в данном случае это параметр, который представляет id компании и который автоматически передается инфаструктурой EF Core.

Если навигационное свойство представляет одиночный объект, то можно применять метод Reference:

using (ApplicationContext db = new ApplicationContext())
{
    User? user = db.Users.FirstOrDefault();  // получаем первого пользователя
    if (user != null)
    {
        db.Entry(user).Reference(u => u.Company).Load();
        Console.WriteLine($"{user.Name} - {user.Company?.Name}");   // Tom - Microsoft
    }
}

На уровне базы данных этот запрос будет транслироваться в следующую команду sql:

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

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

После загрузки данных мы можем повторно обращаться к ним через свойство Local. Например:

using (ApplicationContext db = new ApplicationContext())
{
    Company? company1 = db.Companies.Find(1);
    if (company1 != null)
    {
        db.Users.Where(u => u.CompanyId == company1.Id).Load();
        foreach (var u in company1.Users) Console.WriteLine($"User: {u.Name}");
    }

    Company? company2 = db.Companies.Find(2);
    if (company2 != null)
    {
        db.Users.Where(u => u.CompanyId == company2.Id).Load();
        foreach (var u in company2.Users) Console.WriteLine($"User: {u.Name}");
    }
    // получаем всех сотрудников
    foreach (var u in db.Users) Console.WriteLine($"User: {u.Name}");
}

В данном случае я специально по отдельного загружаю все данные. Предполагается, что компании имеют в базе данных id 1 и 2. Загрузка первой компании и ее сотрудников:

Company? company1 = db.Companies.Find(1);
db.Users.Where(u => u.CompanyId == company1.Id).Load();

что приведет к выполнению двух запросов к базе данных:

SELECT "c"."Id", "c"."Name"
FROM "Companies" AS "c"
WHERE "c"."Id" = @__p_0
LIMIT 1

SELECT "u"."Id", "u"."CompanyId", "u"."Name"
FROM "Users" AS "u"
WHERE "u"."CompanyId" = @__company1_Id_0

То есть сначала получаем компанию, а затем ее сотрудников. При загрузке второй компании и ее сотрудников с помощью кода:

Company? company2 = db.Companies.Find(2);
db.Users.Where(u => u.CompanyId == company2.Id).Load();

Выполняются аналогичные SQL-запросы:

SELECT "c"."Id", "c"."Name"
FROM "Companies" AS "c"
WHERE "c"."Id" = @__p_0
LIMIT 1

SELECT "u"."Id", "u"."CompanyId", "u"."Name"
FROM "Users" AS "u"
WHERE "u"."CompanyId" = @__company2_Id_0

Так как у нас всего 2 компании, и все пользователи принадлежат одной из этих компаний, то мы ожидаем, что все сотрудники уже загружены в контекст, и их не надо заново получать. И далее мы проходим по всем пользователям для вывода их имени на консоль:

foreach (var u in db.Users) Console.WriteLine($"User: {u.Name}");

Несмотря на то, что все данные фактически загружены, выполняется еще один SQL-запрос:

SELECT "u"."Id", "u"."CompanyId", "u"."Name"
FROM "Users" AS "u"

Чтобы избежать этой ситуации, изменим код загрузки пользователей и получим данные, которые были загружены ранее, с помощью свойства Local:

// получаем данные из локального контекста
foreach (var u in db.Users.Local) Console.WriteLine($"User: {u.Name}");

В данном случае никаких дополнительных SQL-запросов на загрузку пользователей выполняться не будет.

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