Não é possível lançar o objeto do tipo 'System.DBNull' para o tipo 'System.String`

109

Recebi o erro acima em meu aplicativo. Aqui está o código original

public string GetCustomerNumber(Guid id)
{
     string accountNumber = 
          (string)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidmyApp, 
                          CommandType.StoredProcedure, 
                          "GetCustomerNumber", 
                          new SqlParameter("@id", id));
     return accountNumber.ToString();
 }

Eu substituí com

public string GetCustomerNumber(Guid id)
{
   object accountNumber =  
          (object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM, 
                                CommandType.StoredProcedure, 
                                "spx_GetCustomerNumber", 
                                new SqlParameter("@id", id));
    if (accountNumber is System.DBNull)
    {
       return string.Empty;
    }
    else
    {
       return accountNumber.ToString();
    }
}

Existe uma maneira melhor de contornar isso?

Saif Khan
fonte
2
você realmente deve olhar para a resposta de @rein, você economizará muito tempo a longo prazo
roman m

Respostas:

90

Uma forma mais curta pode ser usada:

return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()

EDIT: Não prestei atenção ao ExecuteScalar. Ele realmente retorna nulo se o campo estiver ausente no resultado de retorno. Portanto, use:

return (accountNumber == null) ? string.Empty : accountNumber.ToString() 
Do utilizador
fonte
3
Isso não vai funcionar - o "accountNumber" não é um valor de banco de dados, mas uma instância regular de "objeto" Plain Old .NET antigo - você precisa verificar o valor "null" normal. O DBNull.Value funcionaria para um SqlDataReader ou SqlParameter - mas não para este objeto aqui.
marc_s
Você está certo, comecei a otimizar a parte de verificação de condição, não olhei para a linha antes. Mea culpa.
Usuário de
Há um erro de digitação em sua postagem que não consigo editar porque a edição requer 6 caracteres para serem alterados. Alguém pode alterar accountNumber.TosString () para accountNumber.ToString ()
Eric
@marc_s Dependendo do layout db / query, você precisa verificar qualquer um deles ou até mesmo ambos. Se WHERE não corresponder a nenhuma linha, você obterá um null, se a linha selecionada tiver NULLnessa coluna, o valor de retorno é System.DBNull.
Alexander
No primeiro caso, @Alexander menciona -não corresponde a nenhuma linha- ​​você pode contar com Convert.ToString ou qualquer outro método Convert se estiver satisfeito com o valor que eles retornam ao converter de nulo: string vazia para strings, 0 para valores numéricos, false para booleano, MinValue para DateTime ... msdn.microsoft.com/en-us/library/vstudio/…
Jaime
199

Com uma função genérica simples, você pode tornar isso muito fácil. Basta fazer isso:

return ConvertFromDBVal<string>(accountNumber);

usando a função:

public static T ConvertFromDBVal<T>(object obj)
{
    if (obj == null || obj == DBNull.Value)
    {
        return default(T); // returns the default value for the type
    }
    else
    {
        return (T)obj;
    }
}
rédea
fonte
1
Sim, uma função como essa é a única solução prática. Qualquer tipo de lógica in-line falhará depois de você ter copiado e colado milhares de vezes. :-)
Christian Hayter
3
isso não funcionará se você tentar converter 1 em bool (Convert.ToBoolean (1) funciona bem, embora)
roman m
@roman: então gostaríamos de ter uma verificação adicional (antes de verificar se há nulo) que verifica se há um tipo booleano ...
IAbstract
1
Se você deseja ou precisa usar as funções Converter, isso não está funcionando. Existem vários cenários em que você pode preferir converter para um elenco explícito. @romanm notou um deles. Outra é quando você trabalha com decimais e se preocupa com os diferentes mecanismos de arredondamento que Convert.ToInt32 e (int) usam. O primeiro arredonda para o valor par mais próximo, enquanto a conversão explícita apenas trunca o valor: stackoverflow.com/questions/1608801/… Se possível, eu eliminaria NULLs da combinação, usando a função T-SQL ISNULL
Jaime
2
@Jaime Esta função deve atuar como uma conversão implícita de um tipo de dados SQL para um tipo de dados C # / .net. Se você precisa de um elenco explícito, não use esta função - faça-o explicitamente.
reinício
17

ExecuteScalar irá retornar

  • nulo se não houver conjunto de resultados
  • caso contrário, a primeira coluna da primeira linha do conjunto de resultados, que pode ser DBNull.

Se você sabe que a primeira coluna do conjunto de resultados é uma string, para cobrir todas as bases, você precisa verificar se há nulo e DBNull. Algo como:

object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null) ? String.Empty : accountNumber.ToString();

O código acima se baseia no fato de que DBNull.ToString retorna uma string vazia.

Se accountNumber fosse outro tipo (digamos, inteiro), você precisaria ser mais explícito:

object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null || Convert.IsDBNull(accountNumber) ?     
         (int) accountNumber : 0;

Se você tiver certeza de que seu conjunto de resultados sempre terá pelo menos uma linha (por exemplo, SELECT COUNT (*) ...), você pode ignorar a verificação de nulo.

No seu caso, a mensagem de erro "Não foi possível lançar o objeto do tipo 'System.DBNull' para o tipo 'System.String`" indica que a primeira coluna do conjunto de resultados é um valor DBNUll. Isso é do elenco para a string na primeira linha:

string accountNumber = (string) ... ExecuteScalar(...);

O comentário de Marc_s de que você não precisa verificar DBNull.Value está errado.

Joe
fonte
meu conjunto de resultados nem sempre retornará uma linha.
Saif Khan
6

Você pode usar o operador de coalescência nula do C #

return accountNumber ?? string.Empty;
Nathan Koop
fonte
-1: Isso não compila: o método retorna uma string e accountNumber é um objeto.
Joe
2
return Cmd.ExecuteScalar (). ToString () ?? String.Empty;
Chaitanya
return Cmd.ExecuteScalar (). ToString () fez o trabalho para mim
Taran
3

Existe outra maneira de contornar esse problema. Que tal modificar seu procedimento de loja? usando ISNULL (seu campo, "") a função sql, você pode retornar uma string vazia se o valor de retorno for nulo.

Então você tem seu código limpo como versão original.

Russel Yang
fonte
3

Este é o método genérico que uso para converter qualquer objeto que possa ser um DBNull.Value:

public static T ConvertDBNull<T>(object value, Func<object, T> conversionFunction)
{
    return conversionFunction(value == DBNull.Value ? null : value);
}

uso:

var result = command.ExecuteScalar();

return result.ConvertDBNull(Convert.ToInt32);

mais curta:

return command
    .ExecuteScalar()
    .ConvertDBNull(Convert.ToInt32);
Heras
fonte
2

Suponho que você pode fazer assim:

string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;

Se accountNumber for null, significa que não era DBNull string :)

ppiotrowicz
fonte
Ou return (accountNumber as string) ?? string.Empty;, com accountNumber ainda sendo um object. Se você preferir manter sua chamada de banco de dados em sua própria linha.
Brian
1

String.Concat transforma os valores DBNull e nulos em uma string vazia.

public string GetCustomerNumber(Guid id)
{
   object accountNumber =  
          (object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM, 
                                CommandType.StoredProcedure, 
                                "spx_GetCustomerNumber", 
                                new SqlParameter("@id", id));

    return String.Concat(accountNumber);

 }

No entanto, acho que você perde algo na compreensão do código

Andrea Parodi
fonte
1
O que acontece se você escrever return "" + accountNumber;?
Zev Spitz
0

Já que eu obtive uma instância que não é nula e se eu comparei com DBNULL eu tive exceção Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull', e se eu tentei mudar para comparar com NULL, simplesmente não funcionou (já que DBNull é um objeto) mesmo essa resposta aceita.

Decidi simplesmente usar a palavra-chave 'é'. Portanto, o resultado é muito legível:

data = (item is DBNull) ? String.Empty : item

Remy
fonte
-1

Eu uso uma extensão para eliminar esse problema para mim, que pode ou não ser o que você está procurando.

É assim:

public static class Extensions
{

    public String TrimString(this object item)
    {
        return String.Format("{0}", item).Trim();
    }

}

Nota:

Esta extensão não retorna nullvalores! Se o item for nullou DBNull.Value , ele retornará uma String vazia.

Uso:

public string GetCustomerNumber(Guid id)
{
    var obj = 
        DBSqlHelperFactory.ExecuteScalar(
            connectionStringSplendidmyApp, 
            CommandType.StoredProcedure, 
            "GetCustomerNumber", 
            new SqlParameter("@id", id)
        );
    return obj.TrimString();
}
jp2code
fonte
-2

Converta como

string s = System.DBNull.value.ToString();
Sudhakar Rao
fonte