SQL Data Reader - Behandlung von Nullspaltenwerten


297

Ich verwende einen SQL-Katareader, um POCOs aus einer Datenbank zu erstellen. Der Code funktioniert nur, wenn er auf einen Nullwert in der Datenbank stößt. Wenn beispielsweise die Spalte Vorname in der Datenbank einen Nullwert enthält, wird eine Ausnahme ausgelöst.

employee.FirstName = sqlreader.GetString(indexFirstName);

Was ist in dieser Situation der beste Weg, um mit Nullwerten umzugehen?

Antworten:


470

Sie müssen überprüfen nach IsDBNull:

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

Dies ist Ihre einzige zuverlässige Methode, um diese Situation zu erkennen und zu behandeln.

Ich habe diese Dinge in Erweiterungsmethoden eingeschlossen und neige dazu, einen Standardwert zurückzugeben, wenn die Spalte tatsächlich lautet null:

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

Jetzt können Sie es so nennen:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

und Sie müssen sich nie wieder um eine Ausnahme oder einen nullWert sorgen .


64
Wenn jemand den Spaltennamen anstelle des Index benötigt, können Sie int colIndex = reader.GetOrdinal(fieldname);Folgendes tun: und die SafeGetStringFunktion von @ marc_s leicht überladen .
Ilans

Ich kann nicht glauben, dass ich 2019 hier bin, in VB nicht weniger .......... Danke aber, große Hilfe
JimmyB

Dies kann auch folgendermaßen erfolgen: int ordinal = reader.GetOrdinal ("col_name"); uint? val = reader.IsDBNull (ordinal)? (uint?) null: reader.GetUInt32 (ordinal);
ed22

Hallo Leute! Ich habe versucht zu kopieren und in das Formular einzufügen und bin mit einem Fehler zurückgekehrt. "Die Erweiterungsmethode muss in einer nicht generischen statischen Klasse definiert sein."
Jansen Malaggay

Wenn Sie reader.GetOrindal in SafeGetString verwenden und SafeGetString in einer Schleife verwenden möchten, wie kann dies ohne Leistungseinbußen erreicht werden?
AspUser7724

223

Sie sollten den asOperator in Kombination mit dem ??Operator für Standardwerte verwenden. Werttypen müssen als nullwertfähig gelesen und mit einem Standardwert versehen werden.

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

Der asBediener übernimmt das Casting einschließlich der Prüfung auf DBNull.


6
Wenn jemand die Spalte "Alter" von "int" in "SQL bigint" (c # long) ändert, schlägt Ihr Code stillschweigend fehl, indem er 0 zurückgibt. Die Antwort von ZXX lautet IMO zuverlässiger.
Martin Ørding-Thomsen

Ich frage mich, ob Sie die Standardeinstellung (int) auf -1 anstelle von 0 überschreiben können
Chris

5
@Chris - Sie sollten in der Lage sein, Standard (int) durch -1 zu ersetzen.
Stevehipwell

@ Stevo3000 Du bist richtig! Ich habe das versucht und es hat funktioniert, wie Sie gleich nach dem Posten gesagt haben, aber ich habe vergessen, auf diese Seite zurückzukehren :)
Chris

5
Beachten Sie, dass die Verwendung von "als" hier Indexfehler verbergen kann. Wenn Sie versehentlich verwenden sqlreader[indexAge] as string ?? "", erhalten Sie immer "". Überlegen Sie, ob Sie (int?)sqlreader[indexAge] ?? defaultValuestattdessen wirklich möchten. Wenn sich Ihr SQL ändert, erhalten Sie Ausnahmen anstelle von schlechten Werten. @ Stevo3000: Standard (int) ist 0, nicht -1. @ Chris: Stellen Sie sicher, dass Sie die verwenden, die Sie wirklich wollen.
Me22

30

Für eine Zeichenfolge können Sie einfach die Objektversion (auf die über den Array-Operator zugegriffen wird) umwandeln und eine Nullzeichenfolge für Nullen erhalten:

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

oder

employee.FirstName = sqlreader[indexFirstName] as string;

Wenn Sie für Ganzzahlen in ein nullbares int umwandeln, können Sie GetValueOrDefault () verwenden.

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

oder der Null-Koaleszenz-Operator ( ??).

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

2
Explizite Besetzung wie in Ihrem ersten Beispiel funktioniert nicht. Es wirft den gleichen Fehler
Musefan

@musefan: Ist dein Feld eigentlich ein String? Wenn nicht, wird ein anderer Fehler angezeigt. Dies funktioniert und dies ist kein tatsächlicher Unterschied zwischen den Beispielen 1 und 2 (abgesehen von der Syntax).
Gone Coding

1
@GoneCoding: Ja, es ist eine nullfähige Zeichenfolge, und es ist definitiv ein Fall, bei dem die erste ein Problem verursacht, wenn die zweite funktioniert. Ich stelle mir vor, das Problem wird dadurch verursacht, wie mit Nullwerten umgegangen wird. Wie in sind sie nicht nullsondern ein DBNull-Objekt. Der Unterschied zwischen den beiden Anweisungen besteht darin, dass die erste fehlschlägt, wenn es sich nicht um eine Zeichenfolge handelt, während die zweite nur null zurückgibt, wenn es sich nicht um eine Zeichenfolge handelt.
Musefan

23

IsDbNull(int)ist normalerweise viel langsamer als Methoden wie GetSqlDateTimeund dann zu vergleichen DBNull.Value. Probieren Sie diese Erweiterungsmethoden für aus 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;
}

Verwenden Sie sie wie folgt:

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

5
Ich stelle fest, dass die expliziten Operatoren der System.Data.SqlTypes-Typen überall Fehler auslösen, wenn sie versuchen, diesen Code zu verwenden ...
Tetsujin no Oni

Unter stackoverflow.com/a/21024873/1508467 finden Sie eine Erklärung, warum dies manchmal fehlschlägt (verwenden Sie Val <int>, um eine SQL int-Spalte zu lesen).
Rhys Jones

12

Eine Möglichkeit besteht darin, nach DB-Nullen zu suchen:

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

12

reader.IsDbNull(ColumnIndex) funktioniert so viele Antworten sagt.

Und ich möchte erwähnen, dass es möglicherweise bequemer ist, Typen zu vergleichen, wenn Sie mit Spaltennamen arbeiten.

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

Dies funktioniert auch auf alten Versionen von System.Data und .NET FW
RaSor

11

Ich glaube nicht, dass es einen NULL- Spaltenwert gibt, wenn Zeilen innerhalb eines Datenlesers unter Verwendung des Spaltennamens zurückgegeben werden.

Wenn Sie dies tun, erhalten datareader["columnName"].ToString();Sie immer einen Wert, der eine leere Zeichenfolge sein kann ( String.Emptywenn Sie vergleichen müssen).

Ich würde Folgendes verwenden und mir nicht zu viele Sorgen machen:

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

4
Sie können einen Leser [FieldName] == DBNull.Value machen, um nach NULL zu
suchen

11

Diese Lösung ist weniger herstellerabhängig und funktioniert mit einem SQL-, OleDB- und MySQL-Reader:

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
Kopieren und Anpassen dieses Codes direkt in eine Erweiterungsklasse.
Qxotk

8

Ich neige dazu, die Nullwerte in der SELECT-Anweisung durch etwas Passendes zu ersetzen.

SELECT ISNULL(firstname, '') FROM people

Hier ersetze ich jede Null durch eine leere Zeichenfolge. Ihr Code wird in diesem Fall keinen Fehler auslösen.


Verwenden Sie diese Option, um Nullen zu vermeiden. Ansonsten gefällt mir Sonny Boys Antwort auf Hilfsmethoden.
Keine Rückerstattung Keine Rückgabe

3
Warum eine statische, separate Hilfsmethode? Scheint eine Erweiterungsmethode im SqlDataReader nicht überzeugender und intuitiver zu sein?
marc_s

7

Sie können eine generische Funktion schreiben, um Null zu überprüfen und den Standardwert einzuschließen, wenn er NULL ist. Rufen Sie dies auf, wenn Sie Datareader lesen

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

Verwenden Sie beim Lesen des Datenlesers

                        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

Überprüfen sqlreader.IsDBNull(indexFirstName)Sie dies, bevor Sie versuchen, es zu lesen.


4

Wie erstelle ich Hilfsmethoden?

Für String

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

        return o.ToString();
    }

Verwendung

MyStringConverter(read["indexStringValue"])

Für Int

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

        return Convert.ToInt32(o);
    }

Verwendung

MyIntonverter(read["indexIntValue"])

Für Datum

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

Verwendung

MyDateConverter(read["indexDateValue"])

Hinweis: Für DateTime deklarieren Sie varialbe als

DateTime? variable;

4

Als Ergänzung zur Antwort von marc_s können Sie eine allgemeinere Erweiterungsmethode verwenden, um Werte aus dem SqlDataReader abzurufen:

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

Ich würde diese Methode nicht "SafeGet" nennen, da T eine Struktur ist, die Nullen in den Nicht-Null-Standard für T konvertiert - nicht wirklich sicher. Vielleicht "GetValueOrDefault".
Rhys Jones

@RhysJones Warum sollten Sie in diesem Fall T als Struktur haben? Selbst wenn Sie dies tun, würde ich argumentieren, dass der Nicht-Null-Standard in der Struktur das erwartete Verhalten ist.
Getpsyched

@RhysJones Aber ich stimme zu, dass diese Methode möglicherweise nicht sicher ist und Ausnahmen wie InvalidCastException aus dem SqlDataReader behandeln muss.
Getpsyched

4

Durch Einflussnahme auf die Antwort von getpsyched habe ich eine generische Methode erstellt, die den Spaltenwert anhand seines Namens überprüft

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

Verwendungszweck:

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

Ich weiß nicht wer du bist, aber segne dich. Diese Funktion sparte über drei Stunden Arbeit.
Ramneek Singh vor

3

Ich denke, Sie möchten verwenden:

SqlReader.IsDBNull(indexFirstName)

2

Wir verwenden eine Reihe statischer Methoden, um alle Werte aus unseren Datenlesern herauszuholen. In diesem Fall würden wir also aufrufen. DBUtils.GetString(sqlreader(indexFirstName)) Der Vorteil der Erstellung statischer / gemeinsam genutzter Methoden besteht darin, dass Sie nicht immer und immer wieder dieselben Überprüfungen durchführen müssen ...

Die statischen Methoden enthalten Code zum Überprüfen auf Nullen (siehe andere Antworten auf dieser Seite).


2

Sie können den bedingten Operator verwenden:

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

Wie eine der folgenden Antworten, jedoch 8 Jahre zurück!
Bieralkohol

2

Hier gibt es viele Antworten mit nützlichen Informationen (und einigen falschen Informationen). Ich möchte alles zusammenbringen.

Die kurze Antwort auf die Frage ist, nach DBNull zu suchen - fast alle sind sich in diesem Punkt einig :)

Anstatt eine Hilfsmethode zum Lesen von nullbaren Werten pro SQL-Datentyp zu verwenden, können wir dies mit einer generischen Methode mit viel weniger Code beheben. Sie können jedoch keine einzige generische Methode sowohl für nullbare Werttypen als auch für Referenztypen verwenden. Dies wird ausführlich im nullbaren Typ als generischer Parameter erläutert. und generische C # -Einschränkung für alles, was nullbar ist .

Nach den Antworten von @ZXX und @getpsyched erhalten wir zwei Methoden zum Abrufen von nullbaren Werten, und ich habe eine dritte für Nicht-Null-Werte hinzugefügt (sie vervollständigt den Satz basierend auf der Methodenbenennung).

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

Normalerweise verwende ich Spaltennamen. Ändern Sie diese, wenn Sie Spaltenindizes verwenden. Anhand dieser Methodennamen kann ich feststellen, ob ich erwarte, dass die Daten nullwertfähig sind oder nicht. Dies ist sehr nützlich, wenn ich mir den vor langer Zeit geschriebenen Code anschaue.

Tipps;

  • Wenn die Datenbank keine nullbaren Spalten enthält, wird dieses Problem vermieden. Wenn Sie die Kontrolle über die Datenbank haben, sollten Spalten standardmäßig nicht null und nur bei Bedarf nullwertfähig sein.
  • Verwandeln Sie keine Datenbankwerte mit dem Operator C # 'as', da bei einer falschen Umwandlung stillschweigend null zurückgegeben wird.
  • Durch die Verwendung eines Standardwertausdrucks werden Datenbank-Nullwerte für Werttypen wie int, datetime, bit usw. in Nicht-Null-Werte geändert.

Schließlich habe ich beim Testen der oben genannten Methoden für alle SQL Server-Datentypen festgestellt, dass Sie kein Zeichen [] direkt von einem SqlDataReader abrufen können. Wenn Sie ein Zeichen [] möchten, müssen Sie eine Zeichenfolge abrufen und ToCharArray () verwenden.


1

Ich verwende den unten aufgeführten Code, um Nullzellen in einer Excel-Tabelle zu verarbeiten, die in eine Datentabelle eingelesen wird.

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

und / oder ternären Operator mit Zuordnung verwenden:

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

Ersetzen Sie den Standardwert (wenn null) entsprechend jedem Eigenschaftstyp ...


1

Diese Methode ist abhängig von indexFirstName, der die nullbasierte Spaltenordnungszahl sein sollte.

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

Wenn Sie den Spaltenindex nicht kennen, aber keinen Namen überprüfen möchten, können Sie stattdessen diese Erweiterungsmethode verwenden:

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

Und verwenden Sie die Methode wie folgt:

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

1

Alte Frage, aber vielleicht braucht noch jemand eine Antwort

In Wirklichkeit habe ich dieses Problem so umgangen

Für 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;
    }

Das gleiche gilt für die Zeichenfolge. Geben Sie einfach "" anstelle von 0 zurück, da "" eine leere Zeichenfolge ist

so kannst du es gerne benutzen

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

und

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

Sehr flexibel, so dass Sie jede Abfrage einfügen können, um eine Spalte zu lesen, und sie wird niemals mit einem Fehler zurückgegeben


0

Hier ist eine Hilfsklasse, die andere basierend auf der Antwort von @marc_s verwenden können, wenn sie sie benötigen:

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

Konvertieren Sie Griffe DbNull sinnvoll.

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

Beachten Sie, dass DBNull in eine leere Zeichenfolge konvertiert wird, nicht in einen Nullwert.
Rhys Jones

-2

Sie können dies auch jederzeit überprüfen

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

-1 Dies ist nicht der Punkt: Wir behandeln den Fall eines Nullspaltenwerts, nicht den eines Null- oder LeerwertsSqlDataReader
bläulich
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.