Стратегия 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-запросов на загрузку пользователей выполняться не будет.