web-dev-qa-db-fra.com

SQL Data Reader - gestion des valeurs de colonne Null

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

employee.FirstName = sqlreader.GetString(indexFirstName);

Quel est le meilleur moyen de gérer les valeurs NULL dans cette situation?

251
DenaliHardtail

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 encapsulé ces choses dans des méthodes d'extension et j'ai tendance à renvoyer une valeur par défaut si la colonne est bien 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 valeur null.

390
marc_s

Vous devez utiliser l'opérateur as combiné à l'opérateur ?? pour les valeurs par défaut. Les types de valeur devront être lus comme nuls et donner un défaut.

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

L'opérateur as gère le casting, y compris le contrôle de DBNull.

211
stevehipwell

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

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

ou

employee.FirstName = sqlreader[indexFirstName] as string;

Pour les entiers, si vous convertissez en un int 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;
27
Gone Coding

IsDbNull(int) est généralement beaucoup plus lent que d'utiliser des méthodes telles que GetSqlDateTime et de 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]);
18
ZXX

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

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

Je ne pense pas qu'il existe une valeur de colonneNULLlorsque les lignes sont renvoyées dans un datareader à l'aide du nom de colonne.

Si vous faites datareader["columnName"].ToString();, cela vous donnera toujours une valeur qui peut être une chaîne vide (String.Empty si vous devez comparer).

Je voudrais utiliser ce qui suit et ne vous inquiétez pas trop:

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

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

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

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

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);
}
9
Summer-Time

Ce que j'ai tendance à faire est de remplacer les valeurs nulles dans 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 jettera pas d'erreur dans ce cas.

7
alex

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

6
CesarGon

Vous pouvez écrire une fonction générique pour vérifier Null et inclure la valeur par défaut quand il s'agit de NULL. Appelez-le lors de la lecture de 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"]);
                         }
5
Vijai

Je pense que vous voudriez utiliser:

SqlReader.IsDBNull(indexFirstName)
3
bytebender

comment créer des méthodes d'assistance

Pour cordes

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 en tant que

DateTime? variable;
3
Usman Ali

Nous utilisons une série de méthodes statiques pour extraire toutes les valeurs de nos lecteurs de données. Donc, dans ce cas, nous appellerions DBUtils.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 ...

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

2
Sonny Boy

Vous pouvez utiliser l'opérateur conditionnel:

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

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

en vrai je travaillais 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;
    }

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

afin que vous puissiez l'utiliser comme 

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

et

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

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

2
Ahmed M.Kamal

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

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

remplace la valeur par défaut (lorsque null) en fonction de chaque type de propriété ...

1
Charles Bretana
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
xux

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 à partir de SqlDataReader:

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

J'utilise le code ci-dessous pour gérer les cellules nulles dans une feuille Excel lue dans un datatable.

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

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 de la colonne mais 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
Ogglas

En influençant depuis la réponse de getpsyched , j'ai créé une méthode générique qui vérifie la valeur d'une 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")
0
Project Mayhem

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

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

Plutôt que d'utiliser une méthode d'assistance 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 à la fois pour les types de valeur nullable et les types de référence, cela est décrit en détail dans Type nullable comme paramètre générique possible? et C # contrainte de type générique pour tout nullable .

Donc, à la suite des réponses de @ZXX et @getpsyched, nous nous retrouvons avec ceci, 2 méthodes pour obtenir des valeurs nullables et j'ai ajouté une 3ème pour les valeurs non-nulles (cela complète l'ensemble en fonction du nommage de méthode).

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éthodes, je peux dire si je m'attends à ce que les données soient nulles ou non, ce qui est très utile lorsque vous examinez du code écrit il y a longtemps.

Conseils;

  • Ne pas avoir de colonnes Nullable 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-null par défaut et uniquement NULL si nécessaire.
  • Ne convertissez pas les valeurs de base de données avec l'opérateur '#' du C #, car si la conversion est incorrecte, elle renverra silencieusement la valeur null.
  • L'utilisation d'une valeur par défaut expression modifiera les valeurs null de la base de données en valeurs non nulles pour les types de valeur tels que int, date/heure, bit, etc.

Enfin, tout 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 caractère [] d'un SqlDataReader. Si vous voulez un caractère [], vous devrez obtenir une chaîne et utiliser ToCharArray ().

0
Rhys Jones

Voici la classe d'assistance que les autres peuvent utiliser s'ils ont besoin 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
Raghav

Convertir les poignées DbNull de manière sensée.

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