Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > LINQ (Language-Integrated Query) > Проблемма с DefaultIfEmpty()


Автор: BooteR 19.6.2009, 01:16
Доброго времени суток. Скорее всего проблемма не с DefaultIfEmpty(), а с неумением им пользоваться. Гугл перерыл, ничего похожего не нашел, поэтому спрашиваю тут.

Есть несколько табличек в базе. После некоего запроса query получаем таблицу с полями MaterialID и Quantity. Тоесть айди материалла и его необходимое количество. В базе есть таблица Stock. Теперь нужно из query вычесть количество материалов, которое есть на складе. Пробую сделать это таким образом:

Код

var result = from a in query
              select new
             {
                  MaterialId = a.MaterialID,                             
                  Quantity = a.Quantity - dataContext.GetTable<Stock>().Where(n => n.MaterialID == a.MaterialID).Select(n => n.Quantity).DefaultIfEmpty(0).First()
             };


Проблемма в том, что если материала нет на складе, то нужно отнимать 0. Без DefaultIfEmpty() оператор First() выдает exception, потому что входящая последовательность пуста.
А с DefaultIfEmpty(0) выдает exception " Unsupported overload used for query operator 'DefaultIfEmpty' "

Как построить правильно запрос, чтобы небыло проблем когда запись об материале отсутствует в таблице Stock?

Помогите, люди добрые!  smile 
Наперед спасибо

Автор: Idsa 19.6.2009, 05:43
Попробуйте вместо метода DefaultIfEmpty (который не "замаплен" в LINQ To SQL) воспользоваться методом SingleOrDefault.

Автор: KelTron 19.6.2009, 06:55
Разовью идею Idsa.

Код

Quantity = a.Quantity - dataContext.GetTable<Stock>()
    .Where(n => n.MaterialID == a.MaterialID)
    .Select(n => n.Quantity).SingleOrDefault() ?? 0

Автор: Idsa 19.6.2009, 06:58
KelTron, что есть выражение "?? 0"?

Автор: KelTron 19.6.2009, 07:01
Ну есть такой оператор в шарпе)

Ниже два эквивалентных куска кода:

Код

(s != null) ? s : 0;


Код

s ?? 0;

Автор: Idsa 19.6.2009, 07:49
KelTron, а зачем он здесь? SingleOrDefault, если не найдет запись, вернет 0.

Автор: KelTron 19.6.2009, 08:18
Хм, да, ты прав...как то не обратил внимания, что там не класс.

Автор: Idsa 19.6.2009, 08:22
Итого:
Код

Quantity = a.Quantity - dataContext.GetTable<Stock>()
    .Where(n => n.MaterialID == a.MaterialID)
    .Select(n => n.Quantity).SingleOrDefault();


KelTron, спасибо за оператор ?? smile Странно, но ни разу его раньше не встречал.

Автор: KelTron 19.6.2009, 09:11
На здоровье)

Автор: BooteR 19.6.2009, 12:16
Попробовал с SingleOrDefault():

Код

var result = from a in query
                   select new
                   {
                         MaterialId = a.MaterialID,
                         Quantity = a.Quantity - dataContext.GetTable<Stock>().Where(n => n.MaterialID == a.MaterialID).Select(n => n.Quantity).SingleOrDefault()
                   };


Получил exception: "The null value cannot be assigned to a member with type System.Int32 which is a non-nullable value type"

Причем вот это работает правильно:

Код

foreach (var t in query)
{
       System.Diagnostics.Debug.WriteLine(
            t.MaterialID.ToString() + " - " +
            (t.Quantity - dataContext.GetTable<Stock>().Where(n => n.MaterialID == t.MaterialID).Select(n => n.Quantity).SingleOrDefault()).ToString()
       );
}



В чем проблемма? Я вообще запутался :(

Автор: Idsa 19.6.2009, 12:34
BooteR, приведите схему таблицы Stock

Автор: BooteR 19.6.2009, 12:45
Вот:

http://ipicture.ru/

Автор: Idsa 19.6.2009, 12:51
MaterialId, оказывается, не первичный ключ. Тогда не понимаю смысл этого запроса.
Исключение, видимо, вылетает из-за того, что dataContext.GetTable<Stock>().Where(n => n.MaterialID == t.MaterialID) содержит более одного элемента. Раз так, стоит заменить SingleOrDefault на FirstOrDefault.

Автор: BooteR 19.6.2009, 12:58
Я уже не помню почему я не сделал MaterialId первичным ключем, то двух записей с одинаковым MaterialId в таблице нет. Сделал MaterialId первичным ключем, выдает тот же exception. Заменил SingleOrDefault на FirstOrDefault, все та же проблема  smile 
Почему же foreach работает нормально?

Автор: BooteR 19.6.2009, 15:27
Сделал так:

Код

(dataContext.GetTable<Stock>().Where(n => n.MaterialID == a.MaterialID).Select(n => n.Quantity).FirstOrDefault() == null) ? 0 : dataContext.GetTable<Stock>().Where(n => n.MaterialID == a.MaterialID).Select(n => n.Quantity).FirstOrDefault()


Некрасиво, но работает. А вообще странно как то...

Всем спасибо за помощь!

Автор: KelTron 19.6.2009, 15:46
Кажется я понял.

Т.к. внешний запрос отложенный => пока ты не начнёшь перечислять коллекцию он не выполнится и значит 
в поле a.MaterialID будет null.

Вложенный запрос же НЕ отложенный и выполняется немедленно, но в это время ещё нет данных об a.MaterialID.
Поэтому генерится эксепшин.

По этой же причине работает вариант с foreach.

Самое простое что приходит в голову для исправления ситуации:
Код

var result = (from a in query
                   select new
                   {
                         MaterialId = a.MaterialID,
                         Quantity = a.Quantity - dataContext.GetTable<Stock>()
                             .Where(n => n.MaterialID == a.MaterialID).Select(n => .Quantity)
                             .SingleOrDefault()
                   }).ToList();


Автор: BooteR 19.6.2009, 15:59
KelTron, мне результат запроса нужно вывести в DataGridView. Решение с ToList(), как я понимаю, будет проблематично

Автор: KelTron 19.6.2009, 16:49
BooteR, не заметил твоего предыдущего сообщения.

Хм, что то странное, если у тебя n.Quantity это целое не нулевое число, то каким образом там получается null...

Цитата(BooteR @  19.6.2009,  15:27 Найти цитируемый пост)
Некрасиво, но работает. 


Для этого случая как раз и подходит оператор ??

Предыдущий твой код можно написать так:
Код

dataContext.GetTable<Stock>().Where(n => n.MaterialID == a.MaterialID).Select(n => n.Quantity).FirstOrDefault() ?? 0


Что я и предлагал в самом начале... 

Цитата(BooteR @  19.6.2009,  15:27 Найти цитируемый пост)
А вообще странно как то...

Действительно


Автор: BooteR 20.6.2009, 01:41
KelTron, с оператором ?? я пробовал, но выдает ошибку:

Operator '??' cannot be applied to operands of type 'int' and 'int'

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)