Энергичная загрузка связанных данных
В предыдущей главе объяснялось, что сущности, которые связаны через навигационные свойства, могут создаваться в одном запросе с применением энергичной загрузки. Метод
Include()
указывает соединение со связанной сущностью, а метод
ThenInclude()
используется для последующих соединений. Оба метода будут задействованы в рассматриваемых далее тестах. Как упоминалось ранее, когда методы
Include()/ThenInclude()
транслируются в SQL, для обязательных отношений применяется внутреннее соединение, а для необязательных — левое соединение.
Поместите в файл класса
CarTests.cs
следующий тест, чтобы продемонстрировать одиночный вызов
Include()
:
[Fact]
public void ShouldGetAllOfTheCarsWithMakes()
{
IIncludableQueryable<Car, Make?> query =
Context.Cars.<b>Include(c => c.MakeNavigation)</b>;
var queryString = query.ToQueryString();
var cars = query.ToList();
Assert.Equal(9, cars.Count);
}
Тест добавляет к результатам свойство
MakeNavigation
, выполняя внутреннее соединение с помощью показанного ниже кода SQL. Обратите внимание, что глобальный фильтр запросов действует:
SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],
[i].
[TimeStamp],
[m].[Id], [m].[Name], [m].[TimeStamp]
FROM [dbo].[Inventory] AS [i]
INNER JOIN [dbo].[Makes] AS [m] ON [i].[MakeId] = [m].[Id]
WHERE [i].[IsDrivable] = CAST(1 AS bit)
Во втором тесте используется два набора связанных данных. Первый — это получение информации
Make
(как и в предыдущем тесте), а второй — получение сущностей
Order
и затем присоединенных к ним сущностей
Customer
. Полный тест также отфильтровывает записи
Car
, для которых есть записи
Order
. Для необязательных отношений генерируются левые соединения:
[Fact]
public void ShouldGetCarsOnOrderWithRelatedProperties()
{
IIncludableQueryable<Car, Customer?> query = Context.Cars
<b> .Where(c => c.Orders.Any())</b>
<b> .Include(c => c.MakeNavigation)</b>
<b> .Include(c => c.Orders).ThenInclude(o => o.CustomerNavigation);</b>
var queryString = query.ToQueryString();
var cars = query.ToList();
Assert.Equal(4, cars.Count);
cars.ForEach(c =>
{
Assert.NotNull(c.MakeNavigation);
Assert.NotNull(c.Orders.ToList()[0].CustomerNavigation);
});
}
Вот сгенерированный запрос:
SELECT [i].[Id], [i].[Color], [i].[IsDrivable], [i].[MakeId], [i].[PetName],
[i].
[TimeStamp],
[m].[Id], [m].[Name], [m].[TimeStamp], [t0].[Id],
[t0].[CarId], [t0].[CustomerId], [
t0].[TimeStamp], [t0].[Id0],
[t0].[TimeStamp0], [t0].[FirstName], [t0].[FullName],
[t0].[LastName], [t0].[Id1]
FROM [dbo].[Inventory] AS [i]
INNER JOIN [dbo].[Makes] AS [m] ON [i].[MakeId]=[m].[Id]
LEFT JOIN(SELECT [o].[Id], [o].[CarId], [o].[CustomerId],
[o].[TimeStamp],
[c].[Id] AS [Id0], [c].[TimeStamp] AS [TimeStamp0],
[c].[FirstName], [c].[FullName],
[c].[LastName], [t].[Id] AS [Id1]
FROM [dbo].[Orders] AS [o]
INNER JOIN(SELECT [i0].[Id], [i0].[IsDrivable]
FROM [dbo].[Inventory] AS [i0]
WHERE [i0].[IsDrivable]=CAST(1 AS BIT)) AS [t] ON
[o].
[CarId]=[t].[Id]
INNER JOIN [dbo].[Customers] AS [c] ON [o].[CustomerId]=[c].[Id]
WHERE [t].[IsDrivable]=CAST(1 AS BIT)) AS [t0] ON [i].[Id]=[t0].[CarId]
WHERE([i].[IsDrivable]=CAST(1 AS BIT))AND EXISTS (SELECT 1
FROM [dbo].[Orders] AS [o0]
INNER JOIN(SELECT [i1].[Id], [i1].
[Color], [i1].[IsDrivable],
[i1].
[MakeId], [i1].[PetName], [i1].[TimeStamp]
FROM [dbo].[Inventory] AS
[i1]
WHERE [i1].
[IsDrivable]=CAST(1 AS BIT)) AS [t1] ON [o0].[CarId]=[t1].[Id]
WHERE([t1].[IsDrivable]=CAST(1 AS BIT))
AND([i].[Id]=[o0].[CarId]))
ORDER BY [i].[Id], [m].[Id], [t0].[Id], [t0].[Id1], [t0].[Id0];
Разделение запросов к связанным данным
Чем больше соединений добавляется в запрос LINQ, тем сложнее становится результирующий запрос. В версии EF Core 5 появилась возможность выполнять сложные соединения как разделенные запросы. Детальное обсуждение ищите в предыдущей главе, но вкратце помещение в запрос LINQ вызова метода
AsSplitQuery()
инструктирует инфраструктуру EF Core о необходимости разделения одного обращения к базе данных на несколько обращений. В итоге может повыситься эффективность, но возникает риск несогласованности данных. Добавьте в тестовую оснастку приведенный далее тест: