Я наткнулся на очень странное поведение / ошибку LINQ to SQL, которую просто не могу понять.
Для примера возьмем следующие таблицы: Клиенты -> Заказы -> Детали.
Каждая таблица является подтаблицей предыдущей таблицы с регулярными отношениями первичного и внешнего ключей (от 1 до многих).
Если я выполню следующий запрос:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Затем я получаю исключение: Не удалось отформатировать узел «Значение» для выполнения как SQL .
Но следующие запросы не выдают исключение:
var q = from c in context.Customers
select (c.Orders.FirstOrDefault() ?? new Order()).OrderDateTime;
var q = from c in context.Customers
select (new Order()).Details.Count();
Если я изменю свой основной запрос следующим образом, я не получу исключение:
var q = from r in context.Customers.ToList()
select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();
Теперь я мог понять, что последний запрос работает, из-за следующей логики:
Поскольку «SQL» (new) (как я предполагаю здесь) не отображает «new Order ()», мне нужно вместо этого работать с локальным списком.
Но я не могу понять, почему работают два других запроса?!?
Я потенциально могу принять работу с "локальной" версией context.Customers.ToList () , но как ускорить запрос?
Например, в последнем примере запроса я почти уверен, что при каждом выборе будет выполняться новый запрос SQL для получения заказов. Теперь я мог избежать отложенной загрузки, используя DataLoadOptions , но тогда я получал бы тысячи строк Order без какой-либо причины (мне нужна только первая строка) ...
Если бы я мог выполнить весь запрос в одном операторе SQL так, как мне хотелось бы (мой первый пример запроса), то сам механизм SQL был бы достаточно умен, чтобы получить только одну строку Order для каждого Customer ...
Возможно, есть ли способ переписать мой исходный запрос таким образом, чтобы он работал как задумано и выполнялся SQL-сервером одним махом?
EDIT:
(более длинный ответ для Артуро)
Запросы, которые я предоставил, приведены исключительно для примера. Я знаю, что они бессмысленны сами по себе, я просто хотел показать упрощенный пример.
Причина, по которой ваш пример работает, заключается в том, что вы избежали использования "new Order ()" все вместе. Если я немного изменю ваш запрос, чтобы он по-прежнему использовался, я получу исключение:
var results = from e in (from c in db.Customers
select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
select new { e.CustomerID, Count = (e.FirstOrder != null ? e.FirstOrder : new Order()).Details().Count() }
Хотя на этот раз исключение немного отличается - не удалось отформатировать узел 'ClientQuery' для выполнения как SQL.
Если я использую синтаксис ?? вместо (x? Y: z) в этом запросе, я получу то же исключение, которое я получил изначально.
В моем реальном запросе мне не нужно Count () , мне нужно выбрать пару свойств из последней таблицы (которые в моих предыдущих примерах были бы Details). По сути, мне нужно объединить значения всех строк в каждой таблице. Для того, чтобы привести более здоровенный пример, мне сначала придется пересказать мои табели:
Models -> ModelCategoryVariations <- CategoryVariationItems -> CategoryModuleCategoryVariationItemAmounts -> ModelModuleCategoryVariationItemAmountValueChanges
Знак -> представляет отношение 1 -> many . Заметьте, что есть один знак, который наоборот ...
Мой реальный запрос будет выглядеть примерно так:
var q = from m in context.Models
from mcv in m.ModelCategoryVariations
... // select some more tables
select new
{
ModelId = m.Id,
ModelName = m.Name,
CategoryVariationName = mcv.CategoryVariation.Name,
..., // values from other tables
Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
let mmcvia = cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId) ?? new ModelModuleCategoryVariationItemAmount()
select new
{
cvi.Id,
Amount = (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.FirstOrDefault() ?? new ModelModuleCategoryVariationItemAmountValueChange()).Amount
... // select some more properties
}
}
Этот запрос взрывается в строке let mmcvia = .
Если я правильно помню, при использовании let mmcvia = new ModelModuleCategoryVariationItemAmount () , запрос будет взорван при следующем операнде ?? , который равен Amount = .
Если я начинаю запрос с из m в context.Models.ToList () , тогда все работает ...