Impossible de convertir un objet de type «System.DBNull» en type «System.String»

109

J'ai eu l'erreur ci-dessus dans mon application. Voici le code original

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

J'ai remplacé par

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

Y a-t-il un meilleur moyen de contourner cela?

Saif Khan
la source
2
vous devriez vraiment regarder la réponse de @ rein, cela vous fera gagner beaucoup de temps à long terme
roman m

Réponses:

90

Un formulaire plus court peut être utilisé:

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

EDIT: Je n'ai pas fait attention à ExecuteScalar. Il renvoie vraiment null si le champ est absent du résultat renvoyé. Alors utilisez plutôt:

return (accountNumber == null) ? string.Empty : accountNumber.ToString() 
Utilisateur
la source
3
Cela ne fonctionnera pas - le "accountNumber" n'est pas une valeur de base de données mais une ancienne instance "d'objet" .NET ordinaire ancienne - vous devez vérifier par rapport à la valeur "null" normale. Le DBNull.Value fonctionnerait pour un SqlDataReader ou un SqlParameter - mais pas pour cet objet ici.
marc_s
Vous avez raison, j'ai commencé à optimiser la partie vérification de l'état, je n'ai jamais regardé la ligne auparavant. Mea culpa.
Utilisateur
Il y a une faute de frappe dans votre message que je ne peux pas vraiment modifier car la modification nécessite la modification de 6 caractères. Quelqu'un peut-il changer accountNumber.TosString () en accountNumber.ToString ()
Eric
@marc_s Selon la disposition de la base de données / requête, vous devez vérifier l'un ou l'autre ou même les deux. Si le WHERE ne correspond à aucune ligne, vous obtiendrez un null, si la ligne sélectionnée a NULLdans cette colonne, la valeur de retour est System.DBNull.
Alexander
Dans le premier cas, @Alexander mentionne - ne correspondant à aucune ligne - vous pouvez vous fier à Convert.ToString ou à toute autre méthode Convert si vous êtes d'accord avec la valeur qu'ils retournent lors de la conversion de null: chaîne vide pour les chaînes, 0 pour les valeurs numériques, false pour booléen, MinValue pour DateTime ... msdn.microsoft.com/en-us/library/vstudio
Jaime
199

Avec une simple fonction générique, vous pouvez rendre cela très facile. Faites juste ceci:

return ConvertFromDBVal<string>(accountNumber);

en utilisant la fonction:

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êne
la source
1
Oui, une telle fonction est la seule solution pratique. Tout type de logique en ligne échouera après l'avoir copié et collé mille fois. :-)
Christian Hayter
3
cela ne fonctionnera pas si vous essayez de convertir 1 en booléen (Convert.ToBoolean (1) fonctionne bien tho)
roman m
@roman: alors nous voudrions avoir une vérification supplémentaire (avant de vérifier la valeur nulle) qui vérifie un type booléen ...
IAbstract
1
Si vous souhaitez ou devez utiliser les fonctions de conversion, cela ne fonctionne pas. Il existe plusieurs scénarios dans lesquels vous pouvez préférer une conversion en une distribution explicite. @romanm a noté l'un d'entre eux. Un autre est lorsque vous travaillez avec des décimales et que vous vous souciez des différents mécanismes d'arrondi utilisés par Convert.ToInt32 et (int). Le premier arrondit à la valeur paire la plus proche, tandis que la distribution explicite tronque simplement la valeur: stackoverflow.com/questions/1608801/... Si possible, j'éliminerais les NULL du mélange, en utilisant la fonction T-SQL ISNULL
Jaime
2
@Jaime Cette fonction est censée agir comme une conversion implicite d'un type de données SQL vers un type de données C # / .NET. Si vous avez besoin d'un cast explicite, n'utilisez pas cette fonction - faites-le plutôt explicitement.
rein
17

ExecuteScalar retournera

  • null s'il n'y a pas de jeu de résultats
  • sinon, la première colonne de la première ligne de l'ensemble de résultats, qui peut être DBNull.

Si vous savez que la première colonne du jeu de résultats est une chaîne, alors pour couvrir toutes les bases, vous devez vérifier à la fois null et DBNull. Quelque chose comme:

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

Le code ci-dessus repose sur le fait que DBNull.ToString renvoie une chaîne vide.

Si accountNumber était un autre type (par exemple, un entier), vous auriez besoin d'être plus explicite:

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

Si vous savez avec certitude que votre jeu de résultats aura toujours au moins une ligne (par exemple, SELECT COUNT (*) ...), vous pouvez ignorer la vérification de null.

Dans votre cas, le message d'erreur «Impossible de convertir un objet de type« System.DBNull »en type« System.String »» indique que la première colonne de votre jeu de résultats est une valeur DBNUll. C'est de la distribution à la chaîne sur la première ligne:

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

Le commentaire de Marc_s selon lequel vous n'avez pas besoin de vérifier DBNull.Value est faux.

Joe
la source
mon jeu de résultats ne renverra pas toujours une ligne.
Saif Khan
6

Vous pouvez utiliser l'opérateur de fusion nul de C #

return accountNumber ?? string.Empty;
Nathan Koop
la source
-1: Cela ne compile pas: la méthode renvoie une chaîne et accountNumber est un objet.
Joe
2
retourne Cmd.ExecuteScalar (). ToString () ?? String.Empty;
Chaitanya
return Cmd.ExecuteScalar (). ToString () a fait le travail pour moi
Taran
3

Il existe un autre moyen de contourner ce problème. Que diriez-vous de modifier la procédure de votre boutique? en utilisant la fonction SQL ISNULL (votre champ, ""), vous pouvez renvoyer une chaîne vide si la valeur de retour est nulle.

Ensuite, vous avez votre code propre en version originale.

Russel Yang
la source
3

C'est la méthode générique que j'utilise pour convertir tout objet qui pourrait être un DBNull.Value:

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

usage:

var result = command.ExecuteScalar();

return result.ConvertDBNull(Convert.ToInt32);

plus court:

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

Je suppose que vous pouvez le faire comme ceci:

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

Si accountNumber est nul, cela signifie qu'il était DBNull et non une chaîne :)

ppiotrowicz
la source
Ou return (accountNumber as string) ?? string.Empty;, avec accountNumber étant toujours un object. Si vous préférez garder votre appel de base de données sur sa propre ligne.
Brian
1

String.Concat transforme les valeurs DBNull et null en une chaîne vide.

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

    return String.Concat(accountNumber);

 }

Cependant, je pense que vous perdez quelque chose sur la compréhensibilité du code

Andrea Parodi
la source
1
Que se passe-t-il si vous écrivez return "" + accountNumber;?
Zev Spitz
0

Depuis que j'ai une instance qui n'est pas nulle et si je la compare à DBNULL, j'ai Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull' exception, et si j'essayais de changer pour comparer à NULL, cela ne fonctionnait tout simplement pas (puisque DBNull est un objet) même c'est la réponse acceptée.

J'ai décidé d'utiliser simplement le mot-clé «est». Le résultat est donc très lisible:

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

Remy
la source
-1

J'utilise une extension pour éliminer ce problème pour moi, qui peut ou non être ce que vous recherchez.

Ça va comme ça:

public static class Extensions
{

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

}

Remarque:

Cette extension ne renvoie pas denull valeurs! Si l'élément est nullou DBNull.Value , il renverra une chaîne vide.

Usage:

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

Convertissez-le comme

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