Entity Framework Core LINQ проблемы с созданием (существует выбранный случай) запросов - PullRequest
0 голосов
/ 12 декабря 2018

Я пытаюсь перечислить все элементы с дополнительным столбцом, описывающим, принадлежит ли он текущему пользователю.

Поэтому я ищу запрос Linq, который генерирует что-то вроде следующего SQL:

SELECT *,
   CASE WHEN
     EXISTS (
       SELECT NULL FROM OwnedItems
       WHERE OwnedItems.UserId = @UserId AND OwnedItems.ItemId = Items.Id
   )
   THEN 'true'
   ELSE 'false'
   END AS Owned
FROM Items;

В соответствии с Интернетом и успешным экспериментом LinqPad этот код должен работать.

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   Item = item
}

В LinqPad этот код генерирует тот же SQL, что и я.Но в моем проекте это происходит совсем по-другому.

Мой код - это проект .Net Core 2.1, использующий Entity Framework Core 2.1.Поскольку это базовый проект, я не могу напрямую протестировать его в LinqPad, поскольку он еще не поддерживается.

В моем проекте этот код приводит к нефильтрованному оператору SELECT, запрашивающему каждый элемент, а затем для каждого из нихотдельный запрос, чтобы проверить, существует ли он в таблице OwnedItems.Например:

1 экземпляр этого запроса выполняется:

Executed DbCommand (68ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
  SELECT *
  FROM [Items] AS [item]

Далее следуют сотни этих запросов, для выполнения которых требуется несколько секунд:

 Executed DbCommand (32ms) [Parameters=[@__userId_0='?' (DbType = Int32), @_outer_Id='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
  SELECT CASE
      WHEN EXISTS (
          SELECT 1
          FROM [OwnedItems] AS [ownedItems]
          WHERE ([ownedItems].[UserId] = @__userId_0) AND ([ownedItems].[ItemId] = @_outer_Id))
      THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
  END

Дополнительная информация,может быть, это поможет: если я использую ту же строку в качестве части предложения where, она отлично работает.

var q = from item in Items
    where OwnedItems.Any(o => o.UserId == userId && o.ItemId == item.Id)
    select item;

Приведенный выше linq приводит к получению этого замечательного sql:

SELECT *
  FROM [Items] AS [item]
  WHERE EXISTS (
      SELECT 1
      FROM [OwnedItems] AS [o]
      WHERE ([o].[UserId] = @__userId_0) AND ([o].[ItemId] = [item].[Id]))

Примечания:

  • Приведенный выше код был искажен вручную, поэтому там могут быть опечатки.Пожалуйста, не обращайте на них внимания.

  • Я понимаю, что этот конкретный запрос может быть выполнен с использованием левого соединения и проверки на нулевые значения, но мой фактический более сложный, и мне нужны (вложенные) существующие предложения.

ОБНОВЛЕНИЕ ДЛЯ РЕШЕНИЯ

Как указал @KorsG, если Элемент не материализован, генерируется правильный запрос.Я обнаружил, что не материализация Item работает, даже если я напишу следующее:

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   // Item = item  //THIS LINE GENERATES BAD QUERY
   Item = new Item {
       Id = item.Id,
       Name = item.Name,
       ...
       [Literally every single property listed one by one] = item.CorrespondingProperty
       ...
   }
}

Так что я могу на самом деле материализовать полный элемент, мне просто нужно явно ввести каждое последнее свойство.FUN!

Ответы [ 2 ]

0 голосов
/ 12 декабря 2018

Вы должны указать EF загрузить соответствующие данные, в данном случае таблицу OwnedItems .

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

var dataWithRelatedData = db_context.Items.Include(x => x.OwnedItems).Select ...

Другой способ избежать большого количества обращений к базе данных - это загрузить оба набора данных в отдельных запросах, а затем объединить их.в памяти.Таким образом, вы сначала сделаете запрос к Items , а затем с данными вернете другой запрос к OwnedItems и, наконец, объедините их в один список объектов.Это сделало бы только 2 обращения к базе данных, что улучшило бы производительность.

0 голосов
/ 12 декабря 2018

Возможно, вам нужно включить активную загрузку для свойства навигации "OwnedItems" в запросе: https://docs.microsoft.com/en-us/ef/core/querying/related-data#eager-loading

Пожалуйста, опубликуйте свой полный запрос linq, если я приведу пример.

ОБНОВЛЕНИЕ 1

Похоже, у подзапросов есть проблемы N + 1 в EF Core, и это может быть исправлено в версии 3.

Ссылка: https://github.com/aspnet/EntityFrameworkCore/issues/10001

ОБНОВЛЕНИЕ 2

Если вам не нужно полностью материализовать «Предметы», вы должны иметь возможность сделать что-то вроде этого, создав вместо этого аномальный объект, который должен «обмануть» EF вчто вы хотите:

from item in Items
select new
{
   Owned = OwnedItems.Any(own => own.UserId == userId && own.ItemId == item.Id),
   Item = new { Id = item.Id, Name = item.Name }
}

Ссылка: https://github.com/aspnet/EntityFrameworkCore/issues/11186

...