Не удалось отформатировать узел «Значение» для выполнения как SQL - PullRequest
9 голосов
/ 05 мая 2011

Я наткнулся на очень странное поведение / ошибку 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 () , тогда все работает ...

1 Ответ

3 голосов
/ 05 мая 2011

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

Вы можете сделать следующее.

var results = from e in 
                  (from c in db.Customers
                   select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
              select new { e.CustomerID, DetailCount = e.FirstOrder != null ? e.FirstOrder.Details.Count() : 0 };

РЕДАКТИРОВАТЬ:

ОК, я думаю, вы слишком усложнили свой запрос.Проблема в том, что вы используете new WhateverObject() в своем запросе, T-SQL ничего об этом не знает;T-SQL знает о записях на вашем жестком диске, вы выбрасываете то, чего не существует.Об этом знает только C #.НЕ ИСПОЛЬЗУЙТЕ new В ВАШИХ ЗАПРОСАХ ДРУГИХ, ЧЕМ В НАРУШЕННОМ ВЫБРАННОМ ЗАЯВЛЕНИИ, потому что это то, что получит C #, а C # знает о создании новых экземпляров объектов.

Конечно, сработает, если выиспользуйте метод ToList (), но производительность снижается, потому что теперь ваш хост приложения и сервер sql работают вместе, чтобы дать вам результаты, и может потребоваться много вызовов вашей базы данных вместо одного.

Попробуйте вместо этого:

Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
              let mmcvia = 
                   cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(
                        mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId)
              select new
              {
                   cvi.Id,
                   Amount = mmcvia != null ?
                      (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.Select(
                           x => x.Amount).FirstOrDefault() : 0
                   ... // select some more properties
              }

Использование метода Select () позволяет получить первую сумму или ее значение по умолчанию.Я использовал «0» только в качестве примера, я не знаю, каково ваше значение по умолчанию для суммы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...