Как заставить LINQ to SQL оценивать весь запрос в базе данных?

У меня есть запрос, который полностью переводится в SQL. По неизвестным причинам LINQ решает последний Select() выполнить в .NET(не в базе данных), что приводит к запуску большого количества дополнительных SQL-запросов (для каждого элемента) к базе данных.

На самом деле, я нашел странный способ принудительного перевода на SQL:

У меня есть запрос (это действительно упрощенная версия, которая по-прежнему не работает должным образом):

MainCategories.Select(e => new
{
    PlacementId = e.CatalogPlacementId, 
    Translation = Translations.Select(t => new
    {
        Name = t.Name,
        // ...
    }).FirstOrDefault()
})

Он будет генерировать множество SQL-запросов:

SELECT [t0].[CatalogPlacementId] AS [PlacementId]
FROM [dbo].[MainCategories] AS [t0]

SELECT TOP (1) [t0].[Name]
FROM [dbo].[Translations] AS [t0]

SELECT TOP (1) [t0].[Name]
FROM [dbo].[Translations] AS [t0]

...

Однако, если я добавляю еще один Select(), который просто копирует всех членов:

.Select(e => new
{
    PlacementId = e.PlacementId, 
    Translation = new
    {
        Name = e.Translation.Name,
        // ...
    }
})

Он скомпилирует его в один оператор SQL:

SELECT [t0].[CatalogPlacementId] AS [PlacementId], (
    SELECT [t2].[Name]
    FROM (
        SELECT TOP (1) [t1].[Name]
        FROM [dbo].[Translations] AS [t1]
        ) AS [t2]
    ) AS [Name]
FROM [dbo].[MainCategories] AS [t0]

Любые подсказки почему? Как заставить LINQ to SQL генерировать один запрос более общий (без второго копирования Select())?

ПРИМЕЧАНИЕ. Я обновил запрос, чтобы сделать его очень простым.

PS: Только идея, которую я получаю, - это выполнить пост-обработку/преобразование запросов с похожими шаблонами (чтобы добавить еще один Select()).

Ответы

Ответ 1

Когда вы вызываете SingleOrDefault в MyQuery, вы выполняете запрос в тот момент, который загружает результаты в клиент.

SingleOrDefault возвращает IEnumerable<T>, который больше не является IQueryable<T>. В этот момент вы принудили его выполнить всю дальнейшую обработку на клиенте - он больше не может выполнять составление SQL.

Ответ 2

Не совсем уверен, что происходит, но я нахожу, как вы написали этот запрос довольно "странно". Я бы написал это так и предположил, что это сработает:

        var q = from e in MainCategories
                let t = Translations.Where(t => t.Name == "MainCategory" 
                    && t.RowKey == e.Id 
                    && t.Language.Code == "en-US").SingleOrDefault()
                select new TranslatedEntity<Category>
                           {
                               Entity = e,
                               Translation = new TranslationDef
                                                 {
                                                     Language = t.Language.Code,
                                                     Name = t.Name,
                                                     Xml = t.Xml
                                                 }
                           };

Я всегда пытаюсь отделить часть from (выбор источников данных) от части select (проецирование на ваш целевой тип. Я считаю, что это также легче читать/понимать, и обычно оно лучше работает с большинством провайдеров linq.

Ответ 3

Вы можете написать запрос следующим образом, чтобы получить желаемый результат:

MainCategories.Select(e => new
{
    PlacementId = e.CatalogPlacementId, 
    TranslationName = Translations.FirstOrDefault().Name,
})

Насколько мне известно, это связано с тем, как LINQ проектирует запрос. Я думаю, когда он увидит вложенный Select, он не будет проецировать это на несколько подзапросов, поскольку по существу это будет то, что потребуется, поскольку IIRC вы не можете использовать несколько столбцов возврата из подзапроса в SQL, поэтому LINQ изменяет это на запрос за строку. FirstOrDefault с доступом к столбцу, по-видимому, является прямым переводом на то, что произойдет в SQL, и поэтому LINQ-SQL знает, что он может написать подзапрос.

Второй Select должен спроецировать запрос, аналогичный тому, как я написал его выше. Было бы трудно подтвердить, не врываясь в отражатель. Как правило, если мне нужно выбрать много столбцов, я бы использовал инструкцию let, как показано ниже:

from e in MainCategories
let translation = Translations.FirstOrDefault()
select new
{
    PlacementId = e.CatalogPlacementId, 
    Translation = new {
       translation.Name,
    }
})