SQL Data Reader - gestion des valeurs de colonne Null


298

J'utilise un SQLdatareader pour créer des POCO à partir d'une base de données. Le code fonctionne sauf lorsqu'il rencontre une valeur nulle dans la base de données. Par exemple, si la colonne FirstName de la base de données contient une valeur nulle, une exception est levée.

employee.FirstName = sqlreader.GetString(indexFirstName);

Quelle est la meilleure façon de gérer les valeurs nulles dans cette situation?

Réponses:


471

Vous devez vérifier IsDBNull:

if(!SqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

C'est votre seul moyen fiable de détecter et de gérer cette situation.

J'ai enveloppé ces choses dans des méthodes d'extension et j'ai tendance à retourner une valeur par défaut si la colonne est en effet null:

public static string SafeGetString(this SqlDataReader reader, int colIndex)
{
   if(!reader.IsDBNull(colIndex))
       return reader.GetString(colIndex);
   return string.Empty;
}

Maintenant, vous pouvez l'appeler comme ceci:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

et vous n'aurez plus jamais à vous soucier d'une exception ou d'une nullvaleur.


65
Si quelqu'un a besoin du nom de la colonne plutôt que de l'index, vous pouvez le faire: int colIndex = reader.GetOrdinal(fieldname);et surcharger facilement la SafeGetStringfonction de @ marc_s .
ilans

Je ne peux pas croire que je suis ici en 2019, en VB pas moins .......... Merci cependant, grande aide
JimmyB

Peut également être fait comme ceci: int ordinal = reader.GetOrdinal ("col_name"); uint? val = reader.IsDBNull (ordinal)? (uint?) null: reader.GetUInt32 (ordinal);
ed22

Bonjour gars! J'ai essayé de copier et de coller dans le formulaire et je suis revenu avec une erreur. Msgstr "La méthode d 'extension doit être définie dans une classe statique non générique.".
Jansen Malaggay

Si vous utilisez reader.GetOrindal à l'intérieur de SafeGetString et que vous souhaitez utiliser SafeGetString à l'intérieur d'une boucle, comment cela peut-il être réalisé sans que les performances soient atteintes?
AspUser7724

223

Vous devez utiliser l' asopérateur combiné avec l' ??opérateur pour les valeurs par défaut. Les types de valeurs devront être lus comme nullables et donnés par défaut.

employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);

L' asopérateur gère le casting, y compris la vérification de DBNull.


6
Si quelqu'un change la colonne Age d'un int en un bigint SQL (c # long), votre code échouera en silence en retournant 0. La réponse de ZXX est IMO plus fiable.
Martin Ørding-Thomsen

Je me demande si vous pouvez remplacer la valeur par défaut (int) par -1 au lieu de 0
Chris

5
@Chris - Vous devriez pouvoir remplacer simplement default (int) par -1.
stevehipwell

@ Stevo3000 Vous avez raison! J'ai essayé et cela a fonctionné comme vous l'avez dit juste après avoir posté, mais j'ai oublié de revenir sur cette page :)
Chris

5
Sachez que l'utilisation de "as" ici peut masquer les erreurs d'index. Si vous utilisez accidentellement sqlreader[indexAge] as string ?? "", vous obtiendrez toujours "". Déterminez si vous voulez vraiment à la (int?)sqlreader[indexAge] ?? defaultValueplace, donc si votre SQL change, vous obtenez des exceptions au lieu de mauvaises valeurs. @ Stevo3000: la valeur par défaut (int) est 0, pas -1. @Chris: Assurez-vous que vous utilisez celui que vous voulez vraiment.
me22

30

Pour une chaîne, vous pouvez simplement transtyper la version de l'objet (accessible à l'aide de l'opérateur de tableau) et terminer avec une chaîne nulle pour null:

employee.FirstName = (string)sqlreader[indexFirstName];

ou

employee.FirstName = sqlreader[indexFirstName] as string;

Pour les entiers, si vous transtypez en un entier nullable, vous pouvez utiliser GetValueOrDefault ()

employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();

ou l'opérateur de coalescence nulle ( ??).

employee.Age = (sqlreader[indexAge] as int?) ?? 0;

2
La distribution explicite, comme dans votre premier exemple, ne fonctionne pas. Il lance la même erreur
musefan

@musefan: Votre champ est-il en fait une chaîne? Sinon, vous obtiendrez une erreur différente. Cela fonctionne et ce ne sont pas des différences réelles entre les exemples 1 et 2 (à part la syntaxe).
Fin du codage

1
@GoneCoding: Oui, c'est une chaîne nullable, et c'est certainement un cas où le premier pose problème lorsque le second fonctionne. J'imagine que le problème est causé par la façon dont les valeurs nulles sont gérées. Comme dans, ils ne sont pasnull mais plutôt un objet DBNull. La différence entre les deux instructions est que la première échouera s'il ne s'agit pas d'une chaîne, tandis que la seconde renverra simplement null si ce n'est pas une chaîne.
musefan

23

IsDbNull(int)est généralement beaucoup plus lent que d'utiliser des méthodes telles GetSqlDateTimeque puis comparer à DBNull.Value. Essayez ces méthodes d'extension pour SqlDataReader.

public static T Def<T>(this SqlDataReader r, int ord)
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return default(T);
    return ((INullable)t).IsNull ? default(T) : (T)t;
}

public static T? Val<T>(this SqlDataReader r, int ord) where T:struct
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? (T?)null : (T)t;
}

public static T Ref<T>(this SqlDataReader r, int ord) where T : class
{
    var t = r.GetSqlValue(ord);
    if (t == DBNull.Value) return null;
    return ((INullable)t).IsNull ? null : (T)t;
}

Utilisez-les comme ceci:

var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);

5
Je trouve que les opérateurs explicites sur les types System.Data.SqlTypes génèrent des erreurs partout en essayant d'utiliser ce code ...
Tetsujin no Oni

Voir stackoverflow.com/a/21024873/1508467 pour une explication de la raison pour laquelle cela échoue parfois (essayez d'utiliser Val <int> pour lire une colonne SQL int).
Rhys Jones

12

Une façon de le faire est de vérifier les valeurs nulles db:

employee.FirstName = (sqlreader.IsDBNull(indexFirstName) 
    ? ""
    : sqlreader.GetString(indexFirstName));

12

reader.IsDbNull(ColumnIndex) fonctionne comme beaucoup de réponses le disent.

Et je tiens à mentionner que si vous travaillez avec des noms de colonne, la simple comparaison des types peut être plus confortable.

if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }

Cela fonctionne également sur les anciennes versions de System.Data et .NET FW
RaSor

11

Je ne pense pas qu'il y ait une valeur de colonne NULL , lorsque les lignes sont retournées dans un datareader en utilisant le nom de la colonne.

Si vous le faites, datareader["columnName"].ToString();il vous donnera toujours une valeur qui peut être une chaîne vide ( String.Emptysi vous avez besoin de comparer).

J'utiliserais ce qui suit et ne m'inquiéterais pas trop:

employee.FirstName = sqlreader["columnNameForFirstName"].ToString();

4
Vous pouvez faire un lecteur [FieldName] == DBNull.Value, pour vérifier les NULL
Ralph Willgoss

11

Cette solution dépend moins du fournisseur et fonctionne avec un lecteur SQL, OleDB et MySQL:

public static string GetStringSafe(this IDataReader reader, int colIndex)
{
    return GetStringSafe(reader, colIndex, string.Empty);
}

public static string GetStringSafe(this IDataReader reader, int colIndex, string defaultValue)
{
    if (!reader.IsDBNull(colIndex))
        return reader.GetString(colIndex);
    else
        return defaultValue;
}

public static string GetStringSafe(this IDataReader reader, string indexName)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName));
}

public static string GetStringSafe(this IDataReader reader, string indexName, string defaultValue)
{
    return GetStringSafe(reader, reader.GetOrdinal(indexName), defaultValue);
}

1
Copier et personnaliser ce code directement dans une classe d'extensions en ce moment.
qxotk

8

Ce que j'ai tendance à faire, c'est de remplacer les valeurs nulles de l'instruction SELECT par quelque chose de approprié.

SELECT ISNULL(firstname, '') FROM people

Ici, je remplace chaque null par une chaîne vide. Votre code ne générera pas d'erreur dans ce cas.


Si possible, utilisez ceci pour éviter les valeurs nulles. Sinon, j'aime la réponse de Sonny Boy sur les méthodes d'assistance.
Aucun remboursement ni retour

3
Pourquoi une méthode d'assistance statique et séparée? Une méthode d'extension sur le SqlDataReader ne semble-t-elle pas plus convaincante et plus intuitive ??
marc_s

7

Vous pouvez écrire une fonction générique pour vérifier Null et inclure la valeur par défaut lorsqu'elle est NULL. Appelez cela lorsque vous lisez Datareader

public T CheckNull<T>(object obj)
        {
            return (obj == DBNull.Value ? default(T) : (T)obj);
        }

Lors de la lecture de l'utilisation de Datareader

                        while (dr.Read())
                        {
                            tblBPN_InTrRecon Bpn = new tblBPN_InTrRecon();
                            Bpn.BPN_Date = CheckNull<DateTime?>(dr["BPN_Date"]);
                            Bpn.Cust_Backorder_Qty = CheckNull<int?>(dr["Cust_Backorder_Qty"]);
                            Bpn.Cust_Min = CheckNull<int?>(dr["Cust_Min"]);
                         }

6

Vérifiez sqlreader.IsDBNull(indexFirstName)avant d'essayer de le lire.


5

En influençant la réponse de getpsyched , j'ai créé une méthode générique qui vérifie la valeur de la colonne par son nom

public static T SafeGet<T>(this System.Data.SqlClient.SqlDataReader reader, string nameOfColumn)
{
  var indexOfColumn = reader.GetOrdinal(nameOfColumn);
  return reader.IsDBNull(indexOfColumn) ? default(T) : reader.GetFieldValue<T>(indexOfColumn);
}

Usage:

var myVariable = SafeGet<string>(reader, "NameOfColumn")

Je ne sais pas qui tu es, mais je te bénis. Cette fonction a permis d'économiser plus de trois heures de travail.
Ramneek Singh

4

comment créer des méthodes d'assistance

Pour chaîne

private static string MyStringConverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return "";

        return o.ToString();
    }

Usage

MyStringConverter(read["indexStringValue"])

Pour Int

 private static int MyIntonverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return 0;

        return Convert.ToInt32(o);
    }

Usage

MyIntonverter(read["indexIntValue"])

Pour la date

private static DateTime? MyDateConverter(object o)
    {
        return (o == DBNull.Value || o == null) ? (DateTime?)null : Convert.ToDateTime(o);
    }

Usage

MyDateConverter(read["indexDateValue"])

Remarque: pour DateTime, déclarez varialbe comme

DateTime? variable;

4

En complément de la réponse de marc_s, vous pouvez utiliser une méthode d'extension plus générique pour obtenir des valeurs du SqlDataReader:

public static T SafeGet<T>(this SqlDataReader reader, int col)
    {
        return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
    }

Je n'appellerais pas cette méthode "SafeGet" car si T est une structure, elle convertira les valeurs nulles en valeurs non nulles par défaut pour T - pas vraiment sûr. Peut-être "GetValueOrDefault".
Rhys Jones

@RhysJones Pourquoi auriez-vous T comme structure dans ce cas? Même si vous le faites, je dirais que la valeur par défaut non nulle dans la structure est le comportement attendu.
getpsyched le

@RhysJones Mais je conviens que cette méthode pourrait ne pas être sûre, elle devrait gérer des exceptions comme InvalidCastException du SqlDataReader.
getpsyched le

3

Je pense que vous voudriez utiliser:

SqlReader.IsDBNull(indexFirstName)

2

Nous utilisons une série de méthodes statiques pour extraire toutes les valeurs de nos lecteurs de données. Dans ce cas, nous appellerionsDBUtils.GetString(sqlreader(indexFirstName)) L'avantage de créer des méthodes statiques / partagées est que vous n'avez pas à faire les mêmes vérifications encore et encore et encore ...

La ou les méthodes statiques contiendraient du code pour vérifier les valeurs nulles (voir les autres réponses sur cette page).


2

Vous pouvez utiliser l'opérateur conditionnel:

employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";

Identique à l'une des réponses ci-dessous, mais avec 8 ans de retard!
beercohol

2

Il y a beaucoup de réponses ici avec des informations utiles (et des informations erronées) répandues, j'aimerais rassembler tout cela.

La réponse courte à la question est de vérifier DBNull - presque tout le monde est d'accord sur ce bit :)

Plutôt que d'utiliser une méthode d'aide pour lire les valeurs nullables par type de données SQL, une méthode générique nous permet de résoudre ce problème avec beaucoup moins de code. Cependant, vous ne pouvez pas avoir une seule méthode générique pour les types de valeur nullable et les types de référence, cela est longuement discuté dans le type Nullable comme paramètre générique possible? et contrainte de type générique C # pour tout ce qui peut être annulé .

Donc, à la suite des réponses de @ZXX et @getpsyched, nous nous retrouvons avec cela, 2 méthodes pour obtenir des valeurs Nullable et j'ai ajouté une 3ème pour les valeurs non Null (elle complète l'ensemble basé sur la dénomination des méthodes).

public static T? GetNullableValueType<T>(this SqlDataReader sqlDataReader, string columnName) where T : struct
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? (T?)null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNullableReferenceType<T>(this SqlDataReader sqlDataReader, string columnName) where T : class
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.IsDBNull(columnOrdinal) ? null : sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

public static T GetNonNullValue<T>(this SqlDataReader sqlDataReader, string columnName)
{
    int columnOrdinal = sqlDataReader.GetOrdinal(columnName);
    return sqlDataReader.GetFieldValue<T>(columnOrdinal);
}

J'utilise généralement des noms de colonnes, modifiez-les si vous utilisez des index de colonnes. Sur la base de ces noms de méthode, je peux dire si je m'attends à ce que les données soient nulles ou non, ce qui est très utile lorsque l'on regarde du code écrit il y a longtemps.

Conseils;

  • Ne pas avoir de colonnes nullables dans la base de données évite ce problème. Si vous avez le contrôle sur la base de données, les colonnes doivent être non nulles par défaut et uniquement nullables si nécessaire.
  • Ne convertissez pas les valeurs de la base de données avec l'opérateur C # 'as' car si le transtypage est incorrect, il retournera silencieusement null.
  • L'utilisation d'une expression de valeur par défaut modifie les valeurs nulles de la base de données en valeurs non nulles pour les types de valeur tels que int, datetime, bit, etc.

Enfin, en testant les méthodes ci-dessus sur tous les types de données SQL Server, j'ai découvert que vous ne pouvez pas obtenir directement un char [] d'un SqlDataReader, si vous voulez un char [], vous devrez obtenir une chaîne et utiliser ToCharArray ().


1

J'utilise le code répertorié ci-dessous pour gérer les cellules nulles dans une feuille Excel qui est lue dans une table de données.

if (!reader.IsDBNull(2))
{
   row["Oracle"] = (string)reader[2];
}

1
private static void Render(IList<ListData> list, IDataReader reader)
        {
            while (reader.Read())
            {

                listData.DownUrl = (reader.GetSchemaTable().Columns["DownUrl"] != null) ? Convert.ToString(reader["DownUrl"]) : null;
                //没有这一列时,让其等于null
                list.Add(listData);
            }
            reader.Close();
        }

1

et / ou utiliser un opérateur ternaire avec affectation:

employee.FirstName = rdr.IsDBNull(indexFirstName))? 
                     String.Empty: rdr.GetString(indexFirstName);

remplacer la valeur par défaut (lorsqu'elle est nulle) comme appropriée pour chaque type de propriété ...


1

Cette méthode dépend de indexFirstName qui doit être l'ordinal de colonne de base zéro.

if(!sqlReader.IsDBNull(indexFirstName))
{
  employee.FirstName = sqlreader.GetString(indexFirstName);
}

Si vous ne connaissez pas l'index des colonnes mais que vous ne voulez pas vérifier un nom, vous pouvez utiliser cette méthode d'extension à la place:

public static class DataRecordExtensions
{
    public static bool HasColumn(this IDataRecord dr, string columnName)
    {
        for (int i=0; i < dr.FieldCount; i++)
        {
            if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
}

Et utilisez la méthode comme ceci:

if(sqlReader.HasColumn("FirstName"))
{
  employee.FirstName = sqlreader["FirstName"];
}

1

Vieille question mais peut-être que quelqu'un a encore besoin d'une réponse

en vrai j'ai travaillé autour de cette question comme ça

Pour int:

public static object GatDataInt(string Query, string Column)
    {
        SqlConnection DBConn = new SqlConnection(ConnectionString);
        if (DBConn.State == ConnectionState.Closed)
            DBConn.Open();
        SqlCommand CMD = new SqlCommand(Query, DBConn);
        SqlDataReader RDR = CMD.ExecuteReader();
        if (RDR.Read())
        {
            var Result = RDR[Column];
            RDR.Close();
            DBConn.Close();
            return Result;
        }
        return 0;
    }

de même pour la chaîne, il suffit de retourner "" au lieu de 0 car "" est une chaîne vide

vous pouvez donc l'utiliser comme

int TotalPoints = GatDataInt(QueryToGetTotalPoints, TotalPointColumn) as int?;

et

string Email = GatDatastring(QueryToGetEmail, EmailColumn) as string;

très flexible pour que vous puissiez insérer n'importe quelle requête pour lire n'importe quelle colonne et elle ne reviendra jamais avec une erreur


0

Voici une classe d'assistance que les autres peuvent utiliser s'ils en ont besoin en fonction de la réponse de @marc_s:

public static class SQLDataReaderExtensions
    {
        public static int SafeGetInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? 0 : dataReader.GetInt32(fieldIndex);
        }

        public static int? SafeGetNullableInt(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as int?;
        }

        public static string SafeGetString(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? string.Empty : dataReader.GetString(fieldIndex);
        }

        public static DateTime? SafeGetNullableDateTime(this SqlDataReader dataReader, string fieldName)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.GetValue(fieldIndex) as DateTime?;
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName)
        {
            return SafeGetBoolean(dataReader, fieldName, false);
        }

        public static bool SafeGetBoolean(this SqlDataReader dataReader, string fieldName, bool defaultValue)
        {
            int fieldIndex = dataReader.GetOrdinal(fieldName);
            return dataReader.IsDBNull(fieldIndex) ? defaultValue : dataReader.GetBoolean(fieldIndex);
        }
    }

0

Convertissez les poignées DbNull de manière sensible.

employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));

Notez que DBNull est converti en une chaîne vide, pas une valeur nulle.
Rhys Jones

-2

vous pouvez également vérifier cela

if(null !=x && x.HasRows)
{ ....}

-1 Ce n'est pas la question: nous traitons le cas d'une valeur de colonne nulle, pas celle d'une valeur nulle ou videSqlDataReader
bleuâtre
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.