Entity Framework: левое соединение с результатом списка - PullRequest
4 голосов
/ 15 июня 2019

Я пытаюсь оптимизировать свои EF-запросы.У меня есть объект под названием Сотрудник.У каждого сотрудника есть список инструментов.В конечном счете, я пытаюсь получить список сотрудников с их инструментами, которые НЕ сломаны.При выполнении моего запроса я вижу, что на сервер делается ДВА вызова: один для сущностей сотрудников и один для списка инструментов.Опять же, я пытаюсь оптимизировать запрос, поэтому сервер получает запрос только один раз.Как я могу это сделать?

Я изучал с помощью LINQ join и как создать LEFT JOIN, но запрос все еще не оптимизирован.

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

public class Employee
{
    public int EmployeeId { get; set; }
    public List<Tool> Tools { get; set; } = new List<Tool>();
    ...
}

public class Tool
{
    public int ToolId { get; set; }
    public bool IsBroken { get; set; } = false;

    public Employee Employee { get; set; }
    public int EmployeeId { get; set; }
    ...
}
var x = (from e in db.Employees.Include(e => e.Tools)
         select new Employee()
         {
             EmployeeId = e.EmployeeId,
             Tools = e.Tools.Where(t => !t.IsBroken).ToList()
         }).ToList();

Этот второй блок кода имитирует то, что я пытаюсь выполнить.Тем не менее, GroupBy (...) оценивается локально на клиентском компьютере.

(from e in db.Employees
 join t in db.Tools.GroupBy(tool => tool.EmployeeId) on e.EmployeeId equals t.Key into empTool
 from et in empTool.DefaultIfEmpty()
 select new Employee()
 {
    EmployeeId = e.EmployeeId,
    Tools = et != null ? et.Where(t => !t.IsBroken).ToList() : null
 }).ToList();

В любом случае, я могу сделать ОДИН вызов на сервер, а также не выполнять мою оценку GroupBy () локальнои вернет ли он список сотрудников с отфильтрованным списком инструментов с инструментами, которые не сломаны?Спасибо.

Ответы [ 2 ]

2 голосов
/ 15 июня 2019

Короче, это невозможно (и я не думаю, что это когда-либо будет).

Если вы действительно хотите контролировать точные вызовы сервера, EF Core просто не для вас.Хотя в EF Core по-прежнему возникают проблемы с некоторым преобразованием запросов LINQ, что приводит к запросу N + 1 или оценке клиента, в основе этого лежит одна вещь: в отличие от EF6, который использует один огромный SQL-запрос объединения для получения результата, EF Core использует один запрос SQL дляосновной набор результатов плюс один SQL-запрос на каждый коррелированный набор результатов.

Это как-то объяснено в Как работают запросы Раздел документации EF Core:

  1. Запрос LINQ обрабатывается Entity Framework Core для сборкипредставление, которое готово для обработки поставщиком базы данных
    • Результат кэшируется, поэтому эту обработку не нужно выполнять каждый раз, когда выполняется запрос
  2. Результат передается поставщику базы данных
    • Поставщик базы данных определяет, какие части запроса могут быть оценены в базе данных
    • Эти части запроса переводятся на язык запросов конкретной базы данных (например,SQL для реляционной базы данных)
    • Один или несколько запросов отправляются в базу данных, и возвращается набор результатов (результаты являются значениями из базы данных, а не экземплярами сущностей)

Обратите внимание на слово more в последнем пункте.

В вашем случае у вас есть 1 основной набор результатов (Employee) + 1 correпоздний набор результатов (Tool), следовательно, ожидаемые запросы к серверу ДВА (кроме случаев, когда первый запрос возвращает пустой набор).

1 голос
/ 15 июня 2019

Вы можете использовать это:

    var x = from e in _context.Employees
    select new
    {
        e,
        Tools = from tool in e.Tools where !tool.IsBroken select tool
    };
    var result = x.AsEnumerable().Select(y => y.e);

Что будет окончательно преобразовано в SQL-запрос, как показано ниже, в зависимости от вашего провайдера:

    SELECT
`Project1`.`EmployeeId`, 
`Project1`.`Name`, 
`Project1`.`C1`, 
`Project1`.`ToolId`, 
`Project1`.`IsBroken`, 
`Project1`.`EmployeeId1`
FROM (SELECT
`Extent1`.`EmployeeId`, 
`Extent1`.`Name`, 
`Extent2`.`ToolId`, 
`Extent2`.`IsBroken`, 
`Extent2`.`EmployeeId` AS `EmployeeId1`, 
CASE WHEN (`Extent2`.`ToolId` IS NOT NULL) THEN (1)  ELSE (NULL) END AS `C1`
FROM `Employees` AS `Extent1` LEFT OUTER JOIN `Tools` AS `Extent2` ON (`Extent1`.`EmployeeId` = `Extent2`.`EmployeeId`) AND (`Extent2`.`IsBroken` != 1)) AS `Project1`
 ORDER BY 
`Project1`.`EmployeeId` ASC, 
`Project1`.`C1` ASC

Я изменяю свой предыдущий ответ, который был неправильнымСпасибо за комментарии.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...