SQL Data Reader - obsługa wartości kolumny Null

297

Korzystam z SQLdatareader do tworzenia POCO z bazy danych. Kod działa, z wyjątkiem przypadków napotkania wartości zerowej w bazie danych. Na przykład jeśli kolumna FirstName w bazie danych zawiera wartość null, zgłaszany jest wyjątek.

employee.FirstName = sqlreader.GetString(indexFirstName);

Jaki jest najlepszy sposób obsługi wartości zerowych w tej sytuacji?

DenaliHardtail
źródło

Odpowiedzi:

470

Musisz sprawdzić IsDBNull:

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

To twój jedyny niezawodny sposób na wykrycie i rozwiązanie tej sytuacji.

Zawinąłem te rzeczy w metodach rozszerzenia i zwykle zwracam wartość domyślną, jeśli kolumna rzeczywiście null:

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

Teraz możesz to tak nazwać:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

i nigdy więcej nie będziesz musiał się martwić o wyjątek lub nullwartość.

marc_s
źródło
64
Jeśli ktoś potrzebuje nazwy kolumny zamiast indeksu, możesz zrobić: int colIndex = reader.GetOrdinal(fieldname);i łatwo przeciążyć funkcję @ marc_s SafeGetString.
ilans
Nie mogę uwierzyć, że jestem tutaj w 2019 roku, w VB nie mniej .......... Dzięki, świetna pomoc
JimmyB
Można to również zrobić w następujący sposób: int ordinal = reader.GetOrdinal ("nazwa_kolumny"); uint? val = reader.IsDBNull (porządkowy)? (uint?) null: reader.GetUInt32 (porządkowy);
ed22
Cześć chłopaki! Próbowałem skopiować i wkleić do formularza i wróciłem z błędem. „Metodę rozszerzenia należy zdefiniować w nieogólnej klasie statycznej.”.
Jansen Malaggay
Jeśli korzystasz z reader.GetOrindal w SafeGetString i chcesz korzystać z SafeGetString w pętli, w jaki sposób można to osiągnąć bez pogorszenia wydajności?
AspUser7724,
223

Należy użyć asoperatora w połączeniu z ??operatorem dla wartości domyślnych. Typy wartości będą musiały być odczytywane jako wartości zerowe i mieć wartość domyślną.

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

asOperator obsługuje casting tym czekiem na DBNull.

stevehipwell
źródło
6
Jeśli ktoś zmieni kolumnę Wiek z int na bigint SQL (długość c #), twój kod nie powiedzie się po cichu, zwracając 0. Odpowiedź od ZXX jest bardziej niezawodna IMO.
Martin Ørding-Thomsen
Zastanawiam się, czy możesz przesłonić wartość domyślną (int) na -1 zamiast 0
Chris
5
@Chris - Powinieneś być w stanie po prostu zastąpić default (int) na -1.
stevehipwell
@ Stevo3000 Masz rację! Próbowałem tego i zadziałało to tak, jak powiedziałeś zaraz po opublikowaniu, ale zapomniałem wrócić do tej strony :)
Chris
5
Pamiętaj, że użycie tutaj „as” może ukryć błędy indeksu. Jeśli przypadkowo użyjesz sqlreader[indexAge] as string ?? "", zawsze dostaniesz "". Zastanów się, czy naprawdę (int?)sqlreader[indexAge] ?? defaultValuetego chcesz , więc jeśli zmienisz SQL, otrzymasz wyjątki zamiast złych wartości. @ Stevo3000: domyślnie (int) wynosi 0, a nie -1. @Chris: Upewnij się, że używasz tego, którego naprawdę chcesz.
me22
30

W przypadku ciągu możesz po prostu rzutować wersję obiektu (dostępną za pomocą operatora tablicowego) i zakończyć ciągiem zerowym dla wartości zerowych:

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

lub

employee.FirstName = sqlreader[indexFirstName] as string;

W przypadku liczb całkowitych, jeśli rzutujesz na zerowy int, możesz użyć GetValueOrDefault ()

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

lub operator zerowego koalescencji ( ??).

employee.Age = (sqlreader[indexAge] as int?) ?? 0;
Gone Coding
źródło
2
Jawna obsada, jak w pierwszym przykładzie, nie działa.
Zgłasza
@musefan: Czy twoje pole jest w rzeczywistości łańcuchem? Jeśli nie, pojawi się inny błąd. Działa to i nie ma rzeczywistej różnicy między przykładami 1 i 2 (oprócz składni).
Gone Coding
1
@GoneCoding: Tak, jest to łańcuch zerowy, i zdecydowanie jest to przypadek, że pierwszy powoduje problem, gdy drugi działa. Wyobrażam sobie, że problem jest spowodowany tym, jak obsługiwane są wartości zerowe. Jak w, nie są one, nullale zamiast tego obiektem DBNull. Różnica między tymi dwiema instrukcjami polega na tym, że pierwsza zawiedzie, jeśli nie jest łańcuchem, a druga zwróci null, jeśli nie będzie łańcuchem.
musefan
23

IsDbNull(int) jest zwykle znacznie wolniejszy niż przy użyciu metod takich jak GetSqlDateTime a następnie porównywanie DBNull.Value. Wypróbuj następujące metody rozszerzenia dla 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;
}

Użyj ich w ten sposób:

var dd = r.Val<DateTime>(ords[4]);
var ii = r.Def<int>(ords[0]);
int nn = r.Def<int>(ords[0]);
ZXX
źródło
5
Zauważyłem, że jawne operatory typów System.Data.SqlTypes zgłaszają błędy wszędzie, próbując użyć tego kodu ...
Tetsujin no Oni
Zobacz stackoverflow.com/a/21024873/1508467, aby uzyskać wyjaśnienie, dlaczego czasami się to nie udaje (spróbuj użyć Val <int>, aby odczytać kolumnę int SQL).
Rhys Jones
12

Jednym ze sposobów na to jest sprawdzenie zerowych wartości db:

employee.FirstName = (sqlreader.IsDBNull(indexFirstName) 
    ? ""
    : sqlreader.GetString(indexFirstName));
Michael Todd
źródło
12

reader.IsDbNull(ColumnIndex) działa, jak mówi wiele odpowiedzi.

I chcę wspomnieć, jeśli pracujesz z nazwami kolumn, po prostu porównywanie typów może być wygodniejsze.

if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
PJ3
źródło
Działa to również na starszych wersjach System.Data i .NET FW
RaSor
11

Nie sądzę, że jest NULL kolumny , gdy wiersze są zwracane w centrum danych przy użyciu nazwy kolumny.

Jeśli to zrobisz datareader["columnName"].ToString();, zawsze otrzymasz wartość, która może być pustym ciągiem (String.Empty jeśli musisz porównać).

Użyłbym następujących i nie martwiłbym się zbytnio:

employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
El Bayames
źródło
4
Możesz zrobić czytnik [FieldName] == DBNull.Value, aby sprawdzić NULL
Ralph Willgoss
11

To rozwiązanie jest mniej zależne od dostawcy i działa z SQL, OleDB i 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);
}
Czas letni
źródło
1
Kopiowanie i dostosowywanie tego kodu bezpośrednio do klasy rozszerzeń już teraz.
qxotk
8

To, co zwykle robię, to zastępowanie wartości zerowych w instrukcji SELECT czymś odpowiednim.

SELECT ISNULL(firstname, '') FROM people

Tutaj zastępuję każdy null pustym łańcuchem. W takim przypadku kod nie spowoduje błędu.

alex
źródło
Jeśli to możliwe, użyj tego unikaj wartości zerowych. W przeciwnym razie podoba mi się odpowiedź Sonny'ego Boya na metody pomocnicze.
Bez zwrotów Bez zwrotów
3
Dlaczego statyczna, osobna metoda pomocnicza? Czy metoda rozszerzenia SqlDataReader nie wydaje się bardziej przekonująca i bardziej intuicyjna?
marc_s
7

Możesz napisać funkcję Ogólną, aby sprawdzić Null i dołączyć wartość domyślną, gdy jest NULL. Zadzwoń do tego podczas czytania Datareader

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

Podczas czytania skorzystaj z 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"]);
                         }
Vijai
źródło
6

Sprawdź, sqlreader.IsDBNull(indexFirstName)zanim spróbujesz go przeczytać.

CesarGon
źródło
4

jak o tworzeniu metod pomocniczych

Na ciąg

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

        return o.ToString();
    }

Stosowanie

MyStringConverter(read["indexStringValue"])

Dla Int

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

        return Convert.ToInt32(o);
    }

Stosowanie

MyIntonverter(read["indexIntValue"])

Na datę

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

Stosowanie

MyDateConverter(read["indexDateValue"])

Uwaga: dla DateTime zadeklaruj varialbe jako

DateTime? variable;
Usman Ali
źródło
4

Jako dodatek do odpowiedzi autorstwa marc_s można użyć bardziej ogólnej metody rozszerzenia, aby uzyskać wartości z SqlDataReader:

public static T SafeGet<T>(this SqlDataReader reader, int col)
    {
        return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
    }
getpsyched
źródło
Nie nazwałbym tej metody „SafeGet”, ponieważ jeśli T jest strukturą, przekonwertuje wartości NULL na domyślną wartość inną niż N dla T - niezbyt bezpieczne. Być może „GetValueOrDefault”.
Rhys Jones
@RhysJones Dlaczego miałbyś mieć T jako strukturę w tym przypadku? Nawet jeśli to zrobisz, argumentowałbym, że niepustym domyślnym ustawieniem w strukturze jest oczekiwane zachowanie.
getpsyched
@RhysJones Ale zgadzam się, że ta metoda może nie być bezpieczna, musiałaby obsługiwać wyjątki takie jak InvalidCastException od SqlDataReader.
getpsyched
4

Wpływając z getpsyched na odpowiedź , stworzyłem metody rodzajowe, których wartość kolumna sprawdza jego nazwa

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

Stosowanie:

var myVariable = SafeGet<string>(reader, "NameOfColumn")
Projekt Mayhem
źródło
Nie wiem kim jesteś, ale błogosławię cię. Ta funkcja pozwoliła zaoszczędzić ponad trzy godziny pracy.
Ramneek Singh
3

Myślę, że chcesz użyć:

SqlReader.IsDBNull(indexFirstName)
bytebender
źródło
2

Używamy szeregu metod statycznych, aby wyciągnąć wszystkie wartości z naszych czytników danych. Więc w tym przypadku dzwonilibyśmyDBUtils.GetString(sqlreader(indexFirstName)) . Korzyścią z tworzenia metod statycznych / współużytkowanych jest to, że nie musisz ciągle wykonywać tych samych kontroli ...

Metody statyczne zawierałyby kod do sprawdzania wartości zerowych (zobacz inne odpowiedzi na tej stronie).

Sonny Boy
źródło
2

Możesz użyć operatora warunkowego:

employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
Panayot Minkov
źródło
Taka sama jak jedna z poniższych odpowiedzi, ale 8 lat wstecz!
alkohol piwny
2

Jest tu wiele odpowiedzi z użytecznymi informacjami (i kilkoma błędnymi informacjami), chciałbym zebrać je wszystkie razem.

Krótka odpowiedź na pytanie to sprawdzenie DBNull - prawie wszyscy zgadzają się w tej kwestii :)

Zamiast używać metody pomocniczej do odczytywania wartości zerowych dla każdego typu danych SQL, ogólna metoda pozwala nam rozwiązać ten problem przy znacznie mniejszym kodzie. Jednak nie możesz mieć jednej ogólnej metody zarówno dla wartości dopuszczających wartości zerowe, jak i typów referencyjnych. Jest to omówione szczegółowo w typie dopuszczającym wartości zerowe jako możliwy parametr ogólny? i ogólne ograniczenie typu C # dla wszystkiego, co ma wartość null .

Tak więc, podążając za odpowiedziami z @ZXX i @getpsyched, otrzymujemy 2 metody uzyskiwania wartości zerowych, a ja dodałem 3 dla wartości innych niż null (uzupełnia zestaw na podstawie nazewnictwa metod).

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

Zwykle używam nazw kolumn, zmieniam je, jeśli używasz indeksów kolumn. Na podstawie tych nazw metod mogę stwierdzić, czy oczekuję, że dane będą zerowalne, czy nie, co jest całkiem przydatne, gdy patrzę na kod napisany dawno temu.

Porady;

  • Brak zerowalnych kolumn w bazie danych pozwala uniknąć tego problemu. Jeśli masz kontrolę nad bazą danych, kolumny powinny domyślnie mieć wartość inną niż null, a w razie potrzeby nulla.
  • Nie rzutuj wartości bazy danych za pomocą operatora C # jako, ponieważ jeśli rzutowanie jest nieprawidłowe, po cichu zwróci wartość null.
  • Użycie domyślnego wyrażenia wartości zmieni wartości zerowe bazy danych na wartości inne niż null dla typów wartości takich jak int, datetime, bit itp.

Wreszcie, podczas testowania powyższych metod we wszystkich typach danych SQL Server odkryłem, że nie możesz bezpośrednio uzyskać char [] z SqlDataReader, jeśli chcesz char [], musisz uzyskać ciąg znaków i użyć ToCharArray ().

Rhys Jones
źródło
1

Używam kodu wymienionego poniżej do obsługi pustych komórek w arkuszu Excel, który jest wczytywany do danych.

if (!reader.IsDBNull(2))
{
   row["Oracle"] = (string)reader[2];
}
Tequila
źródło
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();
        }
xux
źródło
1

i / lub użyj operatora trójskładnikowego z przypisaniem:

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

zamień wartość domyślną (gdy null) odpowiednio dla każdego typu właściwości ...

Charles Bretana
źródło
1

Ta metoda zależy od indexFirstName, która powinna być liczbą porządkową kolumny liczoną od zera.

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

Jeśli nie znasz indeksu kolumny, ale nie chcesz sprawdzić nazwy, możesz zamiast tego użyć tej metody rozszerzenia:

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

I zastosuj następującą metodę:

if(sqlReader.HasColumn("FirstName"))
{
  employee.FirstName = sqlreader["FirstName"];
}
Ogglas
źródło
1

Stare pytanie, ale może ktoś nadal potrzebuje odpowiedzi

w rzeczywistości pracowałem nad tym problemem w ten sposób

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

to samo dla ciągu po prostu zwróć „” zamiast 0, ponieważ „” jest pustym ciągiem

więc możesz go używać jak

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

i

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

bardzo elastyczny, dzięki czemu można wstawić dowolne zapytanie w celu odczytania dowolnej kolumny i nigdy nie zwróci ono z błędem

Ahmed Kamal
źródło
0

Oto klasa pomocnicza, której inni mogą użyć, jeśli będą potrzebować, na podstawie odpowiedzi @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);
        }
    }
Raghav
źródło
0

Konwertuj uchwyty DbNull rozsądnie.

employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
Frank Hagenson
źródło
Zauważ, że DBNull jest konwertowany na pusty ciąg, a nie na wartość zerową.
Rhys Jones
-2

możesz to również sprawdzić

if(null !=x && x.HasRows)
{ ....}
Scooby
źródło
-1 Nie o to chodzi: zajmujemy się przypadkiem wartości pustej kolumny, a nie pustej lub pustejSqlDataReader
niebieskawy