Не удалось преобразовать тип значения Int32, поскольку материализованное значение равно нулю

193

У меня есть следующий код. Я получаю ошибку:

«Приведение к типу значения Int32 не выполнено, поскольку материализованное значение имеет значение NULL. Либо универсальный параметр типа результата, либо запрос должен использовать обнуляемый тип».

когда таблица CreditHistory не имеет записей.

var creditsSum = (from u in context.User
                  join ch in context.CreditHistory on u.ID equals ch.UserID                                        
                  where u.ID == userID
                  select ch.Amount).Sum();

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

Зосим
источник

Ответы:

330

Запрос linq-to-sql не выполняется как код, а переводится в SQL. Иногда это «дырявая абстракция», которая приводит к неожиданному поведению.

Одним из таких случаев является обработка нуля, когда в разных местах могут возникать неожиданные нули. ...DefaultIfEmpty(0).Sum(0)может помочь в этом (довольно простом) случае, когда не может быть никаких элементов и SUMвозвратов sql, nullтогда как c # ожидает 0.

Более общий подход заключается в использовании, ??которое будет преобразовано COALESCEвсякий раз, когда существует риск того, что сгенерированный SQL вернет неожиданный нуль:

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select (int?)ch.Amount).Sum() ?? 0;

Сначала int?это говорит C # компилятору, что это выражение действительно может возвращаться null, даже если Sum()возвращает int. Затем мы используем нормальный ??оператор для обработки nullслучая.

Основываясь на этом ответе, я написал сообщение в блоге с подробной информацией о LINQ to SQL и LINQ to Entities.

Андерс Абель
источник
3
спасибо Андерс, решение с DefaultIfEmpty (0) .Sum () прекрасно работает для меня. Я также попробовал второе решение с (int?) ... ?? 0 ..., но выдает то же исключение, что и раньше ..
zosim
Наконец-то дошли руки, чтобы проверить это и настроить его, так что теперь вторая версия тоже работает.
Андерс Абель
1
Sum () и другие агрегатные функции будут возвращать ноль при применении к пустому набору данных. Вопреки их определению, в действительности они возвращают обнуляемую версию базового типа.
Suncat2000
2
@recursive: ваш пример - LINQ-to-Objects, а не LINQ-to-SQL (или LINQ-to-Entities). Их основные поставщики данных заставляют их вести себя по-другому.
Suncat2000
Это была хорошая идея. Я обновил свой возвращаемый объект, чтобы иметь обнуляемые свойства, и это сработало.
Кремена Лалова
8

Чтобы разрешить обнуляемое Amountполе, просто используйте оператор объединения нулей, чтобы преобразовать нули в 0.

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch.Amount ?? 0).Sum();
рекурсивный
источник
1
когда я использую ваш совет, компилятор говорит: оператор '??' не может быть применен к операндам типа 'int' и 'int'. я что-то забыл?
zosim
@zosim: Это причина, чтобы добавить актерский состав int? первую очередь.
Андерс Абель
я добавил int ?, но это же исключение. Я буду благодарен вам, когда у вас будет dev env. чтобы проверить, что не так в этом синтаксисе.
zosim
1
@zosim: я не понимаю проблемы. Если Amountэтоint , то мы уже уверены, что оно не может быть нулевым, и объединение не требуется. Если вы получаете сообщение об ошибке, которое вы сказали, то Amountэто не обнуляемое значение, а просто значение. intВ этом случае, возможно, вам нужно изменить свой столбец linq2sql dbml в конструкторе, чтобы разрешить нулевые значения.
рекурсивный
1
@recursive: сумма int, все в порядке. Сумма уже имеет значение. Я думаю, что ошибка выше произошла потому, что таблица CreditHistory пуста. У меня есть одна запись в таблице пользователей и 0 записей в таблице CreditHistory, и произошла ошибка. Когда я использую DefaultIfEmpty (0) .Sum (), он работает нормально, но с ?? 0 выкидывает ошибку. Мой другой вопрос, что является лучшей практикой в ​​этом случае? DefaultIfEmpty (0)? спасибо
zosim
4

Вы используете aggregateфункцию, которая не получает элементы для выполнения действия, вы должны убедиться, что запрос linq дает некоторый результат, как показано ниже:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0
Ashwini
источник
11
Это заставит SDV выполнить дважды. Что не то, что вы хотите для IQueryables
Оди
4

Было это сообщение об ошибке, когда я пытался выбрать из представления.

Проблема заключалась в том, что представление недавно получило несколько новых пустых строк (в столбце SubscriberId), и оно не было обновлено в EDMX (сначала база данных EF).

Столбец должен был иметь тип Nullable, чтобы он работал.

var dealer = Context.Dealers.Where (x => x.dealerCode == dealerCode) .FirstOrDefault ();

Перед обновлением просмотра:

public int SubscriberId { get; set; }

После обновления просмотра:

public Nullable<int> SubscriberId { get; set; }

Удаление и добавление представления обратно в EDMX сработало.

Надеюсь, это кому-нибудь поможет.

живи любя
источник
Это был также мой вопрос и мой ответ
Саймон Николс
4

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

var packesCount = await botContext.Sales.Where(s => s.CustomerId == cust.CustomerId && s.Validated)
                                .SumAsync(s => (int?)s.PackesCount);
                            if(packesCount != null)
                            {
                                // your code
                            }
                            else
                            {
                                // your code
                            }
MohammadSoori
источник
1

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

var credits = from u in context.User
              join ch in context.CreditHistory 
                  on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch;

var creditSum= credits.Sum(x => (int?)x.Amount) ?? 0;
LCJ
источник
0

Получил эту ошибку в Entity Framework 6 с этим кодом во время выполнения:

var fileEventsSum = db.ImportInformations.Sum(x => x.FileEvents)

Обновление от LeandroSoares:

Используйте это для одиночного выполнения:

var fileEventsSum = db.ImportInformations.Sum(x => (int?)x.FileEvents) ?? 0

Оригинал:

Поменял на это и тогда все заработало:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;
Ogglas
источник
1
Разве это не выполнит это дважды?
Nawfal
Это не хороший ответ. Он будет извлекать из БД дважды.
Леандро Соареш
@nawfal Это правда, но это намного лучше, чем ошибка времени выполнения. Вы можете абсолютно использовать linq-to-sql, но с лямбдой это сложнее. Конечно, вы можете поймать исключение, но я думаю, что решение хуже, чем два исполнения.
Огглас
@LeandroSoares см. Выше комментарий
Ogglas
1
@ LeandroSoares Хороший! Я обновил свой ответ и использовал предоставленный вами код и описание, почему использовать его вместо этого.
Огглас
0

Я также столкнулся с той же проблемой и решил с помощью «?» Сделать столбец как обнуляемый. оператор.

Sequnce = db.mstquestionbanks.Where(x => x.IsDeleted == false && x.OrignalFormID == OriginalFormIDint).Select(x=><b>(int?)x.Sequence</b>).Max().ToString();

Иногда ноль возвращается.

user3820036
источник