Почему следующий запрос linq to sql генерирует подзапрос?

Я выполнил следующий запрос:

var list = from book in books
          where book.price > 50
          select book;

list = list.Take(50);

Я бы ожидал, что приведенное выше сгенерирует что-то вроде:

SELECT top 50 id, title, price, author
FROM Books
WHERE price > 50

но он генерирует:

SELECT
[Limit1].[C1] as [C1]
[Limit1].[id] as [Id], 
[Limit1].[title] as [title], 
[Limit1].[price] as [price], 
[Limit1].[author]
FROM (SELECT TOP (50) 
             [Extent1].[id] as as [Id], 
             [Extent1].[title] as [title], 
             [Extent1].[price] as [price], 
             [Extent1].[author] as [author]
      FROM Books as [Extent1]
      WHERE [Extent1].[price] > 50
     ) AS [Limit1]

Почему вышеупомянутый запрос linq создает подзапрос и откуда приходит C1?

Ответ 1

Вы все равно можете сделать его более чистым:

var c = (from co in db.countries
                    where co.regionID == 5
                    select co).Take(50);

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

Table(country).Where(co => (co.regionID = Convert(5))).Take(50)

Эквивалент:

SELECT TOP (50) [t0].[countryID], [t0].[regionID], [t0].[countryName], [t0].[code]
FROM [dbo].[countries] AS [t0]
WHERE [t0].[regionID] = 5

EDIT: Комментарии, это не обязательно потому, что с помощью отдельного Take() вы все равно можете использовать его следующим образом:

var c = (from co in db.countries
                     where co.regionID == 5
                     select co);
            var l = c.Take(50).ToList();

И результат будет таким же, как и раньше.

SELECT TOP (50) [t0].[countryID], [t0].[regionID], [t0].[countryName], [t0].[code]
FROM [dbo].[countries] AS [t0]
WHERE [t0].[regionID] = @p0

Тот факт, что вы написали IQueryable = IQueryable.Take(50), является сложной частью здесь.

Ответ 2

Отказ от ответственности: Я никогда не использовал LINQ раньше...

Мое предположение - поддержка подкачки? Я предполагаю, что у вас есть какой-то метод Take(50, 50), который получает 50 записей, начиная с записи 50. Взгляните на SQL, который генерирует запрос, и вы, вероятно, обнаружите, что он использует аналогичную структуру подзапроса, чтобы позволить ей возвращать любые 50 строк в запросе примерно в течение времени, в течение которого он возвращает первые 50 строк.

В любом случае вложенный дочерний запрос не добавляет никаких служебных накладных расходов, поскольку он автоматически оптимизируется во время компиляции плана выполнения.

Ответ 3

Подзапрос создается для целей проецирования, имеет смысл, когда вы выбираете из нескольких таблиц в один анонимный объект, тогда внешний запрос используется для сбора результатов.

Попробуйте что-то вроде этого:

from book in books
where price > 50
select new 
{
  Title = book.title,
  Chapters = from chapter in book.Chapters
             select chapter.Title
}

Ответ 4

Разве это не случай первого запроса, возвращающего общее количество строк, а второй извлекает подмножество строк на основе вызова метода .Take()?

Ответ 5

  • Я согласен с @Justin Swartsel. Не было ошибки, так что это в значительной степени академический вопрос.
  • Linq-to-SQL стремится генерировать SQL, который работает эффективно (что было в вашем случае).
    • Но он не прилагает никаких усилий для создания обычного SQL, который, вероятно, создаст человек.
  • У реалистов Linq-to-SQL, вероятно, использовался шаблон построителя для генерации SQL.
    • Если это так, было бы проще добавить подстроку (или подзапрос в этом случае), чем было бы возвращать назад и вставить фрагмент "TOP x" в предложение SELECT.