SQL Data Reader: gestione dei valori delle colonne Null


298

Sto usando un SQLdatareader per creare POCO da un database. Il codice funziona tranne quando incontra un valore null nel database. Ad esempio, se la colonna FirstName nel database contiene un valore null, viene generata un'eccezione.

employee.FirstName = sqlreader.GetString(indexFirstName);

Qual è il modo migliore per gestire valori null in questa situazione?

Risposte:


471

Devi verificare IsDBNull:

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

Questo è l'unico modo affidabile per rilevare e gestire questa situazione.

Ho racchiuso queste cose in metodi di estensione e tendo a restituire un valore predefinito se la colonna è effettivamente null:

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

Ora puoi chiamarlo così:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

e non dovrai mai più preoccuparti di un'eccezione o di un nullvalore.


65
Se qualcuno ha bisogno del nome della colonna anziché dell'indice, puoi fare: int colIndex = reader.GetOrdinal(fieldname);e sovraccaricare facilmente la SafeGetStringfunzione di @ marc_s .
ilans,

Non riesco a credere di essere qui nel 2019, in VB non meno .......... Grazie comunque, grande aiuto
JimmyB,

Inoltre può essere fatto in questo modo: int ordinal = reader.GetOrdinal ("col_name"); uint? val = reader.IsDBNull (ordinale)? (uint?) null: reader.GetUInt32 (ordinale);
ed22

Ciao ragazzi! Ho provato a copiare e incollato nel modulo e ho restituito un errore. "Il metodo di estensione deve essere definito in una classe statica non generica.".
Jansen Malaggay,

Se si utilizza reader.GetOrindal all'interno di SafeGetString e si desidera utilizzare SafeGetString all'interno di un ciclo, come è possibile ottenerlo senza il calo delle prestazioni?
AspUser7724

223

È necessario utilizzare l' asoperatore combinato con l' ??operatore per i valori predefiniti. I tipi di valore dovranno essere letti come nullable e dotati di un valore predefinito.

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

L' asoperatore gestisce il casting incluso il controllo per DBNull.


6
Se qualcuno cambia la colonna Età da un int a un SQL bigint (c # long), il tuo codice fallirà silenziosamente restituendo 0. La risposta di ZXX è IMO più affidabile.
Martin Ørding-Thomsen,

Mi chiedo se è possibile sovrascrivere il valore predefinito (int) in -1 anziché 0
Chris,

5
@Chris - Dovresti essere in grado di sostituire semplicemente il valore predefinito (int) con -1.
stevehipwell,

@ Stevo3000 Hai ragione! Ci ho provato e ha funzionato come hai detto subito dopo aver pubblicato, ma ho dimenticato di tornare a questa pagina :)
Chris,

5
Tenere presente che l'utilizzo di "as" qui può nascondere errori di indice. Se usi accidentalmente sqlreader[indexAge] as string ?? "", otterrai sempre "". Considera se vuoi davvero (int?)sqlreader[indexAge] ?? defaultValue, quindi se il tuo SQL cambia, ottieni eccezioni invece di valori errati. @ Stevo3000: il valore predefinito (int) è 0, non -1. @ Chris: Assicurati di utilizzare quello che desideri davvero.
me22

30

Per una stringa puoi semplicemente eseguire il cast della versione dell'oggetto (accessibile tramite l'operatore di array) e finire con una stringa null per null:

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

o

employee.FirstName = sqlreader[indexFirstName] as string;

Per i numeri interi, se si esegue il cast in un int nullable, è possibile utilizzare GetValueOrDefault ()

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

o l'operatore a coalescenza nulla ( ??).

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

2
Il cast esplicito, come nel tuo primo esempio, non funziona.
Emette

@musefan: il tuo campo è in realtà una stringa? Altrimenti otterrai un errore diverso. Questo funziona e questi non fanno differenza tra gli esempi 1 e 2 (a parte la sintassi).
Codice finito

1
@GoneCoding: Sì, è una stringa nullable, ed è sicuramente un caso il primo causa problemi quando il secondo funziona. Immagino che il problema sia causato da come vengono gestiti i valori null. Come in, non sono nullma invece un oggetto DBNull. La differenza tra le due istruzioni è che la prima avrà esito negativo se non è una stringa, mentre la seconda restituirà null se non è una stringa.
musefan,

23

IsDbNull(int)di solito è molto più lento rispetto all'utilizzo di metodi come GetSqlDateTimee quindi a confronto DBNull.Value. Prova questi metodi di estensione per 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;
}

Usali in questo modo:

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

5
Sto scoprendo che gli operatori espliciti sui tipi System.Data.SqlTypes generano errori ovunque nel tentativo di utilizzare questo codice ...
Tetsujin no Oni,

Vedere stackoverflow.com/a/21024873/1508467 per una spiegazione del motivo per cui a volte non riesce (provare a utilizzare Val <int> per leggere una colonna int SQL).
Rhys Jones,

12

Un modo per farlo è verificare la presenza di null db:

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

12

reader.IsDbNull(ColumnIndex) funziona come dicono molte risposte.

E voglio menzionare se lavori con i nomi delle colonne, solo confrontare i tipi potrebbe essere più comodo.

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

Questo funziona anche con le vecchie versioni di System.Data e .NET FW
RaSor

11

Non credo che ci sia un valore di colonna NULL , quando le righe vengono restituite all'interno di un datareader usando il nome della colonna.

Se lo fai datareader["columnName"].ToString();ti darà sempre un valore che può essere una stringa vuota ( String.Emptyse devi confrontare).

Vorrei usare quanto segue e non mi preoccuperei troppo:

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

4
Puoi fare un lettore [FieldName] == DBNull.Value, per verificare la presenza di NULL
Ralph Willgoss

11

Questa soluzione è meno dipendente dal fornitore e funziona con un lettore SQL, OleDB e 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
Copia e personalizzazione di questo codice direttamente in una classe di estensioni in questo momento.
qxotk,

8

Quello che tendo a fare è sostituire i valori null nell'istruzione SELECT con qualcosa di appropriato.

SELECT ISNULL(firstname, '') FROM people

Qui sostituisco ogni null con una stringa vuota. In questo caso il tuo codice non genererà errori.


Se possibile, utilizzare questo per evitare null. Altrimenti, mi piace la risposta dei metodi di supporto di Sonny Boy.
Nessun rimborso Nessun reso

3
Perché un metodo di supporto statico e separato? Un metodo di estensione su SqlDataReader non sembra più convincente e più intuitivo ??
marc_s

7

È possibile scrivere una funzione generica per controllare Null e includere il valore predefinito quando è NULL. Chiamalo quando leggi Datareader

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

Quando leggi il Datareader usa

                        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

Controlla sqlreader.IsDBNull(indexFirstName)prima di provare a leggerlo.


5

Influenzando dalla risposta di getpsyched , ho creato un metodo generico che il valore della colonna controlli con il suo nome

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

Uso:

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

Non so chi sei, ma ti benedico. Questa funzione ha permesso di risparmiare oltre tre ore di lavoro.
Ramneek Singh,

4

come creare metodi di supporto

Per stringa

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

        return o.ToString();
    }

uso

MyStringConverter(read["indexStringValue"])

Per int

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

        return Convert.ToInt32(o);
    }

uso

MyIntonverter(read["indexIntValue"])

Per data

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

uso

MyDateConverter(read["indexDateValue"])

Nota: per DateTime dichiarare varialbe come

DateTime? variable;

4

Come aggiunta alla risposta di marc_s, è possibile utilizzare un metodo di estensione più generico per ottenere valori da SqlDataReader:

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

Non chiamerei questo metodo "SafeGet" perché se T è una struttura, convertirà i valori null in valori predefiniti non nulli per T - non molto sicuri. Forse "GetValueOrDefault".
Rhys Jones,

@RhysJones Perché dovresti avere T come struttura in questo caso? Anche se lo fai, direi che il valore predefinito non nullo nella struttura è il comportamento previsto.
getpsyched

@RhysJones Ma sono d'accordo sul fatto che questo metodo potrebbe non essere sicuro, dovrebbe gestire eccezioni come InvalidCastException da SqlDataReader.
getpsyched

3

Penso che vorresti usare:

SqlReader.IsDBNull(indexFirstName)

2

Utilizziamo una serie di metodi statici per estrarre tutti i valori dai nostri lettori di dati. Quindi in questo caso chiameremmo DBUtils.GetString(sqlreader(indexFirstName)) Il vantaggio di creare metodi statici / condivisi è che non devi fare gli stessi controlli più e più volte ...

I metodi statici conterrebbero il codice per verificare la presenza di null (vedere altre risposte in questa pagina).


2

È possibile utilizzare l'operatore condizionale:

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

Come una delle risposte di seguito, ma 8 anni indietro!
beercohol

2

Ci sono molte risposte qui con informazioni utili (e alcune informazioni sbagliate) sparse, mi piacerebbe riunire tutto.

La risposta breve alla domanda è verificare DBNull - quasi tutti sono d'accordo su questo bit :)

Invece di utilizzare un metodo di supporto per leggere valori nullable per tipo di dati SQL, un metodo generico ci consente di risolvere questo problema con molto meno codice. Tuttavia, non è possibile disporre di un singolo metodo generico sia per i tipi di valori annullabili sia per i tipi di riferimento, questo è discusso a lungo nel tipo Nullable come parametro generico possibile? e vincolo di tipo generico C # per tutto nullable .

Quindi, seguendo le risposte di @ZXX e @getpsyched, finiamo con questo, 2 metodi per ottenere valori nullable e ho aggiunto un terzo per valori non nulli (completa il set in base alla denominazione del metodo).

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

Di solito uso i nomi di colonna, li altero se usi gli indici di colonna. Sulla base di questi nomi di metodi, posso dire se mi aspetto che i dati siano nulli o meno, molto utili quando si guarda il codice scritto molto tempo fa.

Suggerimenti;

  • Non avere colonne nullable nel database evita questo problema. Se si ha il controllo sul database, le colonne dovrebbero essere non nulle per impostazione predefinita e annullabili solo se necessario.
  • Non eseguire il cast dei valori del database con l'operatore C # 'as' poiché, se il cast è errato, restituirà automaticamente null.
  • L'uso di un'espressione di valore predefinita cambierà i valori nulli del database in valori non nulli per tipi di valore come int, datetime, bit ecc.

Infine, durante il test dei metodi di cui sopra su tutti i tipi di dati di SQL Server ho scoperto che non è possibile ottenere direttamente un carattere [] da un SqlDataReader, se si desidera un carattere [], sarà necessario ottenere una stringa e utilizzare ToCharArray ().


1

Sto usando il codice elencato di seguito per gestire le celle null in un foglio Excel che viene letto in un datatable.

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

e / o utilizzare l'operatore ternario con incarico:

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

sostituire il valore predefinito (se nullo) come appropriato per ciascun tipo di proprietà ...


1

Questo metodo dipende da indexFirstName che dovrebbe essere l'ordinale di colonna a base zero.

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

Se non conosci l'indice di colonna ma non vuoi controllare un nome, puoi usare questo metodo di estensione:

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

E usa il metodo in questo modo:

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

1

Vecchia domanda ma forse qualcuno ha ancora bisogno di una risposta

in realtà ho risolto questo problema in quel modo

Per 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;
    }

lo stesso per la stringa restituisce "" invece di 0 come "" è una stringa vuota

così puoi usarlo come

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

e

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

molto flessibile in modo da poter inserire qualsiasi query per leggere qualsiasi colonna e non restituirà mai con errori


0

Ecco la classe helper che gli altri possono usare se hanno bisogno in base alla risposta di @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

Converti le maniglie DbNull in modo sensato.

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

Si noti che DBNull viene convertito in una stringa vuota, non in un valore null.
Rhys Jones,

-2

puoi mai controllare anche per questo

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

-1 Non è questo il punto: stiamo gestendo il caso di un valore di colonna null, non quello di un valore null o vuotoSqlDataReader
bluastro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.