переписать коррелированные подзапросы в ef core 2.1 - PullRequest
0 голосов
/ 20 октября 2018

Есть ли способ, которым я могу переписать этот запрос, чтобы он не был коррелированным подзапросом?

var query = (from o in dbcontext.Orders
                             let lastStatus = o.OrderStatus.Where(x => x.OrderId == o.Id).OrderByDescending(x => x.CreatedDate).FirstOrDefault()
                             where lastStatus.OrderId != 1
                             select new { o.Name, lastStatus.Id }
                             ).ToList();

Это привело к:

 SELECT [o].[Name], (
          SELECT TOP(1) [x0].[Id]
          FROM [OrderStatus] AS [x0]
          WHERE ([x0].[OrderId] = [o].[Id]) AND ([o].[Id] = [x0].[OrderId])
          ORDER BY [x0].[CreatedDate] DESC
      ) AS [Id]
      FROM [Orders] AS [o]
      WHERE (
          SELECT TOP(1) [x].[OrderId]
          FROM [OrderStatus] AS [x]
          WHERE ([x].[OrderId] = [o].[Id]) AND ([o].[Id] = [x].[OrderId])
          ORDER BY [x].[CreatedDate] DESC
      ) <> 1

Я устал делать соединение наподзапрос, но это EF 2.1 делает проводные вещи ... не то, что я ожидал;

     var query = (from o in dbcontext.Orders
                     join lastStat in (from os in dbcontext.OrderStatus
                                       orderby os.CreatedDate descending
                                       select new { os }
                                       ) on o.Id equals lastStat.os.OrderId
                     where lastStat.os.StatusId != 1
                     select new { o.Name, lastStat.os.StatusId }).ToList();

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

В EF6 замена

let x = (...).FirstOrDefault()

на

from x in (...).Take(1).DefaultIfEmpty()

обычно генерирует лучший SQL.

Поэтому обычно я бы предложил

var query = (from o in db.Set<Order>()
             from lastStatus in o.OrderStatus
                 .OrderByDescending(s => s.CreatedDate)
                 .Take(1)
             where lastStatus.Id != 1
             select new { o.Name, StatusId = lastStatus.Id }
            ).ToList();

(нет необходимости в DefaultIfEmpty (левое соединение), поскольку условие where в любом случае превратит его во внутреннее соединение).

К сожалению, в настоящее время (EF Core 2.1.4) существует проблема перевода, поэтому приведенные выше выводы приводятдля оценки клиента.

Текущий обходной путь - заменить средство доступа к свойству навигации o.OrderStatus коррелированным подзапросом:

var query = (from o in db.Set<Order>()
             from lastStatus in db.Set<OrderStatus>()
                 .Where(s => o.Id == s.OrderId)
                 .OrderByDescending(s => s.CreatedDate)
                 .Take(1)
             where lastStatus.Id != 1
             select new { o.Name, StatusId = lastStatus.Id }
            ).ToList();

, который создает следующий SQL для базы данных SqlServer (боковое соединение):

  SELECT [o].[Name], [t].[Id] AS [StatusId]
  FROM [Orders] AS [o]
  CROSS APPLY (
      SELECT TOP(1) [s].*
      FROM [OrderStatus] AS [s]
      WHERE [s].[OrderId] = [o].[Id]
      ORDER BY [s].[CreatedDate] DESC
  ) AS [t]
  WHERE [t].[Id] <> 1 
0 голосов
/ 20 октября 2018

Я предполагаю, что вы на самом деле извлекаете все Order с, но только их часть (страницу или пакет для обработки).

В этом случае, возможно, лучше разделитьэто в двух запросах (но не проверено):

var orders = dbcontext.Orders.Where(o => /* some filter logic */);
var orderIds = orders.Select(o => o.OrderId).ToList();

// get status for latest change - this should query OrderStatus only
var statusNameMap = dbContext.OrderStatus
   .Where(os => orderIds.Contains(Id))
   .GroupBy(os => os.OrderId)
   .Select(grp => grp.OrderByDescending(grp => grp.CreatedDate).First())
   .ToDictionary(os => os.OrderId, os => os.StatusId);

// aggregate the results
// the orders might fetch only the needed columns to have less data on the wire
var result = orders.
    .ToList()
    .Select(o => new { o.Name, statusNameMap[o.OrderId] });

Я не думаю, что запросы будут лучше, но может быть легче понять, что здесь происходит.

Если выдействительно нужно обработать все Order s, и у вас их много (или много Status es), вы можете рассмотреть возможность сохранения столбца LastStatusId непосредственно в таблице Order (это следует обновлять при каждом изменении статуса).

...