Левые операторы внешнего соединения, содержащие справочные свойства навигации, найденные во всех разделенных запросах

У меня есть объект, который содержит несколько ссылочных свойств навигации.

Реализация репозитория для объекта выглядит примерно так:

return await _dbContext.MyEntity
                                     .Include(s => s.Address) //Reference Navigation
                                     .Include(s => s.BuildingDetails) //Reference Navigation
                                        .ThenInclude(s => s.ChildOfBuildingDetails)
                                     .Include(s => s.ContactPersons)
                                     .Include(s => s.Technicians)
                                     .Include(s => s.DeactivationDetails)  //Reference Navigation
                                     .FirstOrDefaultAsync(s => s.Id == id, cancellationToken);

Когда я проверяю фактические выполняемые запросы к БД, все запросы содержат ссылочные свойства навигации, включенные в них как соединения с родительским объектом.

SELECT [m92].[Id], .......
FROM [MyDB].[ContactPersons] AS [m92]
INNER JOIN (
    SELECT DISTINCT [m93].[Id], [t76].[Id] AS [Id0]
    FROM [MyDB].[MyEntity] AS [m93]
    LEFT JOIN (
        SELECT [m94].*
        FROM [MyDB].[DeactivationDetails] AS [m94]
        WHERE [m94].[Deleted] = 0
    ) AS [t75] ON [m93].[Id] = [t75].[MyEntityId]
    LEFT JOIN (
        SELECT [m95].*
        FROM [MyDB].[BuildingDetails] AS [m95]
        WHERE [m95].[Deleted] = 0
    ) AS [t76] ON [m93].[Id] = [t76].[MyEntityId]
    LEFT JOIN (
        SELECT [m96].*
        FROM [MyDB].[Address] AS [m96]
        WHERE [m96].[Deleted] = 0
    ) AS [t77] ON [m93].[Id] = [t77].[MyEntityId]
    WHERE [m93].[Deleted] = 0
) AS [t78] ON [m92].[MyEntityId] = [t78].[Id]
WHERE [m92].[Deleted] = 0
ORDER BY [t78].[Id], [t78].[Id0]

По сути, вся часть внутри INNER JOIN присутствует во всех выполняемых запросах. В идеале нам нужно только присоединиться к дочерним объектам с родительским объектом в запросах.

1) Почему ядро ​​EF преобразуется в запросы таким образом, что оно включает свойство навигации по ссылкам во всех разделенных запросах?

2) Есть ли способ избежать этого поведения, а именно, заменить блок INNER JOIN только родительским объектом


person roshan    schedule 14.06.2020    source источник
comment
(1) Дефект EF Core (2) Нет, поскольку (1) (3) В версии 3.0 это будет один запрос, но он может выполняться в разы медленнее из-за декартова произведения наборов данных.   -  person Ivan Stoev    schedule 14.06.2020
comment
@IvanStoev Да, ты прав. При обновлении до .NET Core 3.1 мы столкнулись с падением производительности из-за декартового взрыва. Выполнение запроса в некоторых случаях сократилось с нескольких миллисекунд до 5–6 минут, поэтому нам пришлось понизить версию только EF Core до версии 2.2 (при сохранении версии .NET Core 3.1 для приложения).   -  person roshan    schedule 15.06.2020


Ответы (1)


1) Почему ядро ​​EF преобразуется в запросы таким образом, что оно включает свойство навигации по ссылкам во всех разделенных запросах?

Это дефект реализации/отсутствие оптимизации.

2) Есть ли способ избежать этого поведения, а именно, заменить блок INNER JOIN только родительским объектом

Единственный способ, который я нашел, - это материализовать запрос с удаленным свойством навигации коллекции (которые генерируют дополнительные запросы), а затем вручную выполнить запросы для загрузки связанных коллекций (требуется отслеживание запросов и зависит от исправления свойства навигации).

Например (при условии, что свойства навигации, не отмеченные как ссылки, являются коллекциями):

// Query with filters only
var query = _dbContext.MyEntity
    .Where(s => s.Id == id);

// Execute and materialize query with only filters and reference includes
var result = await query
    .Include(s => s.Address) //Reference Navigation
    .Include(s => s.BuildingDetails) //Reference Navigation
        //.ThenInclude(s => s.ChildOfBuildingDetails)
    //.Include(s => s.ContactPersons)
    //.Include(s => s.Technicians)
    .Include(s => s.DeactivationDetails)  //Reference Navigation
    .FirstOrDefaultAsync(cancellationToken);

// Load the related collections
await query.SelectMany(s => s.BuildingDetails.ChildOfBuildingDetails)
    .LoadAsync(cancellationToken);
await query.SelectMany(s => s.ContactPersons)
    .LoadAsync(cancellationToken);
await query.SelectMany(s => s.Technicians)
    .LoadAsync(cancellationToken);
person Ivan Stoev    schedule 15.06.2020