Как заставить 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,
}
})