Функции агрегирования вложенных таблиц ef-core выдают сообщение «Ошибка NavigationExpandingExpressionVisitor» - PullRequest
1 голос
/ 14 января 2020

У меня есть вложенная модель данных, и я хотел бы получить агрегированные данные из нее, сгруппированные по свойству верхнего уровня. Например, мои модели:

public class Scan {
    public long Id {get; set;}
    public int ProjectId { get; set; }
    public int ScanUserId { get; set; }
    public ICollection<ScanItem>? Items { get; set; }
}

public class ScanItem
{
    public long Id { get; set; }
    public long InventoryScanId { get; set; }
    public double Qty { get; set; }
}

Я бы хотел получить все сканы, сгруппированные по Scan.ScanUserId, а затем получить сумму ScanItems.Qty, например, для каждого пользователя. Мой запрос выглядит следующим образом, и EF выдает следующую ошибку:

Обработка выражения LINQ 'AsQueryable ((необработанный параметр: x) .Items) "с помощью NavigationExpandingExpressionVisitor' не выполнена

from scan in Scans
        .Include(x=>x.ScanUser)
        .Include(x=>x.Items)
    group scan by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
    select new
    {
        UserId = g.Key.Id,
        Name = g.Key.Name,
        LastSyncTime = g.Max(x => x.ScanDate),
        ScanItems = g.Sum(x=>x.Items.Sum(i=>i.Qty))
    }

Как запустить статистические функции для свойств вложенной таблицы, не оценивая ее на стороне клиента?

1 Ответ

3 голосов
/ 14 января 2020

EF Core по-прежнему не может преобразовать вложенные агрегаты в результат GroupBy (группировка).

Необходимо предварительно рассчитать вложенные агрегаты, используя селектор элементов GroupBy (или * 1005). * в синтаксисе запроса group element by key):

from scan in Scans
group new { scan.ScanDate, Qty = scan.Items.Sum(i => i.Qty) } // <--
by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
select new
{
    UserId = g.Key.Id,
    Name = g.Key.Name,
    LastSyncTime = g.Max(x => x.ScanDate),
    ScanItems = g.Sum(x => x.Qty) // <--
}

** Обновление: ** для SqlServer указанный выше запрос LINQ преобразуется в SQL запрос, подобный следующему:

 SELECT [s1].[Id] AS [UserId], [s1].[Name], MAX([s0].[ScanDate]) AS [LastSyncTime], SUM((
      SELECT SUM([s].[Qty])
      FROM [ScanItem] AS [s]
      WHERE [s0].[Id] = [s].[InventoryScanId])) AS [ScanItems]
  FROM [Scan] AS [s0]
  INNER JOIN [ScanUser] AS [s1] ON [s0].[ScanUserId] = [s1].[Id]
  GROUP BY [s1].[Name], [s1].[Id]

, который как упомянутое в комментарии генерирует SQL исключение выполнения "Невозможно выполнить агрегатную функцию для выражения, содержащего агрегат или подзапрос." .

Так что вам действительно нужен другой подход - используйте left join чтобы сгладить результирующий набор перед группировкой, а затем выполнить группировку / агрегаты для этого набора:

from scan in Scans
from item in scan.Items.DefaultIfEmpty() // <-- left outer join
group new { scan, item } by new { scan.ScanUser.Name, scan.ScanUser.Id } into g
select new
{
    UserId = g.Key.Id,
    Name = g.Key.Name,
    LastSyncTime = g.Max(x => x.scan.ScanDate),
    ScanItems = g.Sum(x => (double?)x.item.Qty) ?? 0
};

, что теперь переводится в, как мы надеемся, действительный SqlServer SQL запрос:

  SELECT [s1].[Id] AS [UserId], [s1].[Name], MAX([s].[ScanDate]) AS [LastSyncTime], COALESCE(SUM([s0].[Qty]), 0.0E0) AS [ScanItems]
  FROM [Scan] AS [s]
  LEFT JOIN [ScanItem] AS [s0] ON [s].[Id] = [s0].[InventoryScanId]
  INNER JOIN [ScanUser] AS [s1] ON [s].[ScanUserId] = [s1].[Id]
  GROUP BY [s1].[Name], [s1].[Id]
...