A conversão para o tipo de valor 'Int32' falhou porque o valor materializado é nulo

192

Eu tenho o seguinte código. Estou recebendo erro:

"A conversão para o tipo de valor 'Int32' falhou porque o valor materializado é nulo. O parâmetro genérico do tipo de resultado ou a consulta devem usar um tipo anulável."

quando a tabela CreditHistory não possui registros.

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();

Como posso modificar a consulta para aceitar valores nulos?

zosim
fonte

Respostas:

328

Uma consulta linq para sql não é executada como código, mas traduzida para SQL. Às vezes, essa é uma "abstração com vazamento" que gera comportamento inesperado.

Um desses casos é o tratamento nulo, onde pode haver nulos inesperados em locais diferentes. ...DefaultIfEmpty(0).Sum(0)pode ajudar nesse caso (bastante simples), em que pode não haver elementos e SUMretornos do sql, nullenquanto c # espera 0.

Uma abordagem mais geral é usar a ??qual será traduzida COALESCEsempre que houver o risco de o SQL gerado retornar um nulo inesperado:

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;

Isso inicia para int?informar ao compilador C # que essa expressão pode realmente retornar null, mesmo que Sum()retorne um int. Em seguida, usamos o ??operador normal para lidar com o nullcaso.

Com base nesta resposta, escrevi uma postagem no blog com detalhes para o LINQ to SQL e o LINQ to Entities.

Anders Abel
fonte
3
obrigado Anders, a solução com DefaultIfEmpty (0) .Sum () funciona bem para mim. Eu também tentei a segunda solução com (int?) ... ?? 0 ..., mas ele lança a mesma exceção como antes ..
Zosim
Finalmente cheguei a testar isso e o ajustei, agora a segunda versão também funciona.
Anders Abel
1
Sum () e outras funções agregadas retornarão nulas quando aplicadas a um conjunto de dados vazio. Ao contrário de sua definição, na realidade eles retornam uma versão anulável do tipo subjacente.
Suncat2000
2
@recursive: Seu exemplo é LINQ-to-Objects, não LINQ-to-SQL (ou LINQ-to-Entities). Seus provedores de dados subjacentes os fazem se comportar de maneira diferente.
precisa saber é o seguinte
Essa foi uma boa ideia. Atualizei meu objeto de retorno para ter propriedades anuláveis ​​e isso funcionou como um encanto.
Kremena Lalova 04/08/2015
8

Para permitir um Amountcampo anulável , basta usar o operador de coalescência nula para converter nulos em 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();
recursivo
fonte
1
quando uso sua dica, o compilador diz: Operator '??' não pode ser aplicado a operandos do tipo 'int' e 'int'. esqueci alguma coisa?
Zosim
@zosim: Essa é a razão para adicionar o elenco int?primeiro.
Anders Abel
eu adicionei int ?, mas a mesma exceção. Serei grato a você, quando você tiver dev env. para verificar o que há de errado nessa sintaxe.
Zosim
1
@zosim: Eu não entendo o problema. Se Amountfor um int, já temos certeza de que não pode ser nulo e a coalescência é desnecessária. Se você está recebendo o erro que você disse, então Amountnão é anulável, é apenas um int; nesse caso, talvez você precise alterar sua coluna linq2sql dbml no designer para permitir nulos.
recursivo
1
@ recursivo: Quantidade é int, tudo bem. O valor já tem valor. Eu acho que o erro acima ocorreu porque a tabela CreditHistory está vazia. Eu tenho um registro na tabela Usuário e 0 registros na tabela CreditHistory e ocorreu um erro. Quando eu uso DefaultIfEmpty (0) .Sum () funciona bem, mas com ?? 0 gera erro. Minha outra pergunta é qual é a melhor prática neste caso? DefaultIfEmpty (0)? obrigado
zosim
4

Você está usando a aggregatefunção que não obtém os itens para executar a ação, você deve verificar se a consulta linq está dando algum resultado, como abaixo:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0
Ashwini
fonte
11
Isso faria o sdv executar duas vezes. O que não é o que você deseja para o IQueryables
Ody
4

Recebi essa mensagem de erro quando estava tentando selecionar uma exibição.

O problema foi que a visualização ganhou recentemente algumas novas linhas nulas (na coluna SubscriberId) e não foi atualizada no EDMX (primeiro no banco de dados EF).

A coluna tinha que ser do tipo Anulável para funcionar.

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

Antes de visualizar a atualização:

public int SubscriberId { get; set; }

Após a atualização da visualização:

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

Excluir e adicionar a visualização novamente no EDMX funcionou.

Espero que ajude alguém.

amor ao vivo
fonte
Este foi também o meu problema e minha resposta
Simon Nicholls
4

Eu usei esse código e ele responde corretamente, apenas o valor de saída é anulável.

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
fonte
1

Vejo que esta pergunta já foi respondida. Mas se você deseja que ele seja dividido em duas instruções, é possível considerar o seguinte.

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
fonte
0

Este erro ocorreu no Entity Framework 6 com este código em tempo de execução:

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

Atualização do LeandroSoares:

Use isto para execução única:

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

Original:

Mudou para isso e funcionou:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;
Ogglas
fonte
1
Isso não seria executado duas vezes?
Nawfal # 22/18
Esta não é uma boa resposta. Ele será recuperado do banco de dados duas vezes.
Leandro Soares
@nawfal Isso é verdade, mas é muito melhor que um erro de execução. Você pode usar absolutamente o linq-to-sql, mas com o lambda é mais difícil. É claro que você pode capturar a exceção, mas acho que a solução é pior que duas execuções.
Ogglas
@LeandroSoares veja o comentário acima
Ogglas
1
@LeandroSoares Nice one! Atualizei minha resposta e usei o código que você forneceu e a descrição do motivo.
Ogglas
0

Eu também estava enfrentando o mesmo problema e resolvi fazendo a coluna anulável usando "?" operador.

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

Às vezes, nulo é retornado.

user3820036
fonte