SQL Data Reader - menangani nilai kolom Null


298

Saya menggunakan SQLdatareader untuk membangun POCO dari database. Kode ini berfungsi kecuali ketika menemukan nilai nol dalam database. Misalnya, jika kolom FirstName dalam database berisi nilai nol, pengecualian dilemparkan.

employee.FirstName = sqlreader.GetString(indexFirstName);

Apa cara terbaik untuk menangani nilai nol dalam situasi ini?

Jawaban:


471

Anda perlu memeriksa IsDBNull:

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

Itulah satu-satunya cara andal Anda untuk mendeteksi dan menangani situasi ini.

Saya membungkus hal-hal itu ke dalam metode ekstensi dan cenderung mengembalikan nilai default jika kolomnya memang null:

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

Sekarang Anda bisa menyebutnya seperti ini:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

dan Anda tidak perlu khawatir tentang pengecualian atau nullnilai lagi.


65
Jika seseorang membutuhkan nama kolom daripada indeks, Anda dapat melakukannya: int colIndex = reader.GetOrdinal(fieldname);dan dengan mudah membebani SafeGetStringfungsi @ marc_s .
ilans

Tidak percaya saya ada di sini pada tahun 2019, di VB tidak kurang .......... Terima kasih, sangat membantu
JimmyB

Juga dapat dilakukan seperti ini: int ordinal = reader.GetOrdinal ("col_name"); kamu tidak val = reader.IsDBNull (ordinal)? (uint?) null: reader.GetUInt32 (ordinal);
ed22

Hallo teman-teman! Saya mencoba menyalin dan menempel ke formulir dan kembali dengan kesalahan. "Metode ekstensi harus didefinisikan dalam kelas statis non-generik."
Jansen Malaggay

Jika Anda menggunakan reader.GetOrindal di dalam SafeGetString dan Anda ingin menggunakan SafeGetString di dalam satu lingkaran, bagaimana hal ini dapat dicapai tanpa kinerja menekan?
AspUser7724

223

Anda harus menggunakan asoperator yang dikombinasikan dengan ??operator untuk nilai default. Tipe nilai perlu dibaca sebagai nullable dan diberi default.

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

The asOperator menangani casting termasuk cek untuk DBNull.


6
Jika seseorang mengubah kolom Usia dari menjadi int ke bigint SQL (c # long), kode Anda akan gagal diam-diam dengan mengembalikan 0. Jawaban dari ZXX adalah IMO yang lebih andal.
Martin Ørding-Thomsen

Saya ingin tahu apakah Anda dapat mengganti default (int) menjadi -1 dan bukan 0
Chris

5
@ Chris - Anda seharusnya bisa mengganti default (int) dengan -1.
stevehipwell

@ Stevo3000 Anda benar! Saya mencobanya dan itu berhasil seperti yang Anda katakan setelah saya memposting, tetapi saya lupa untuk kembali ke halaman ini :)
Chris

5
Ketahuilah bahwa menggunakan "sebagai" di sini dapat menyembunyikan kesalahan indeks. Jika Anda tidak sengaja menggunakan sqlreader[indexAge] as string ?? "", Anda akan selalu mendapatkannya "". Pertimbangkan apakah Anda benar-benar menginginkannya (int?)sqlreader[indexAge] ?? defaultValue, jadi jika SQL Anda berubah, Anda mendapatkan pengecualian dan bukan nilai yang buruk. @ Stevo3000: default (int) adalah 0, bukan -1. @ Chris: Pastikan Anda menggunakan yang Anda inginkan.
me22

30

Untuk string, Anda cukup melemparkan versi objek (diakses menggunakan operator array) dan berakhir dengan string nol untuk nulls:

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

atau

employee.FirstName = sqlreader[indexFirstName] as string;

Untuk bilangan bulat, jika Anda menggunakan int nullable, Anda bisa menggunakan GetValueOrDefault ()

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

atau operator penggabungan nol ( ??).

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

2
Pemeran eksplisit, seperti dalam contoh pertama Anda, tidak berfungsi. Itu melempar kesalahan yang sama
musefan

@musefan: Apakah bidang Anda sebenarnya adalah string? Jika tidak, Anda akan mendapatkan kesalahan yang berbeda. Ini berfungsi dan ini tidak ada perbedaan nyata antara contoh 1 dan 2 (selain dari sintaks).
Hilang Coding

1
@GoneCoding: Ya itu adalah string yang dapat dibatalkan, dan ini jelas merupakan kasus yang pertama menyebabkan masalah ketika yang kedua bekerja. Saya membayangkan masalah ini disebabkan oleh bagaimana nilai-nilai nol ditangani. Seperti dalam, mereka bukan nullmelainkan objek DBNull. Perbedaan antara dua pernyataan adalah yang pertama akan gagal jika itu bukan string, sedangkan yang kedua hanya akan mengembalikan nol jika bukan string.
musefan

23

IsDbNull(int)biasanya jauh lebih lambat daripada menggunakan metode seperti GetSqlDateTimedan kemudian membandingkannya DBNull.Value. Coba metode ekstensi ini untuk 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;
}

Gunakan mereka seperti ini:

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

5
Saya menemukan bahwa operator eksplisit pada tipe System.Data.SqlTypes melempar kesalahan di mana-mana mencoba menggunakan kode ini ...
Tetsujin no Oni

Lihat stackoverflow.com/a/21024873/1508467 untuk penjelasan mengapa ini terkadang gagal (coba gunakan Val <int> untuk membaca kolom int SQL).
Rhys Jones

12

Salah satu cara untuk melakukannya adalah memeriksa db nulls:

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

12

reader.IsDbNull(ColumnIndex) berfungsi seperti banyak jawaban.

Dan saya ingin menyebutkan jika Anda bekerja dengan nama kolom, hanya membandingkan jenis mungkin lebih nyaman.

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

Ini juga berfungsi pada versi lama System.Data dan .NET FW
RaSor

11

Saya tidak berpikir ada nilai kolom NULL , ketika baris dikembalikan dalam datareader menggunakan nama kolom.

Jika Anda melakukannya datareader["columnName"].ToString();akan selalu memberi Anda nilai yang dapat berupa string kosong ( String.Emptyjika Anda perlu membandingkan).

Saya akan menggunakan yang berikut ini dan tidak akan terlalu khawatir:

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

4
Anda dapat melakukan pembaca [FieldName] == DBNull.Value, untuk memeriksa NULL's
Ralph Willgoss

11

Solusi ini tidak terlalu tergantung pada vendor dan berfungsi dengan SQL, OleDB, dan 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
Menyalin dan menyesuaikan kode ini langsung ke kelas ekstensi sekarang.
qxotk

8

Apa yang saya cenderung lakukan adalah mengganti nilai nol dalam pernyataan SELECT dengan sesuatu yang sesuai.

SELECT ISNULL(firstname, '') FROM people

Di sini saya mengganti setiap null dengan string kosong. Kode Anda tidak akan melakukan kesalahan dalam hal itu.


Jika memungkinkan, gunakan ini hindari nulls. Kalau tidak, saya suka jawaban Sonny Boy tentang metode pembantu.
Tidak Ada Pengembalian Uang Tidak Ada Pengembalian

3
Mengapa metode pembantu statis dan terpisah? Bukankah metode ekstensi pada SqlDataReader tampaknya lebih menarik dan lebih intuitif ??
marc_s

7

Anda dapat menulis fungsi Generik untuk memeriksa Null dan memasukkan nilai default ketika NULL. Sebut ini saat membaca Datareader

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

Saat membaca penggunaan 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

Periksa sqlreader.IsDBNull(indexFirstName)sebelum Anda mencoba membacanya.


5

Dengan memengaruhi jawaban getpsyched , saya membuat metode generik yang memeriksa nilai kolom berdasarkan namanya

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

Pemakaian:

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

Saya tidak tahu siapa Anda, tetapi berkatilah Anda. Fungsi ini menghemat lebih dari tiga jam kerja.
Ramneek Singh

4

bagaimana cara membuat metode pembantu

Untuk String

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

        return o.ToString();
    }

Pemakaian

MyStringConverter(read["indexStringValue"])

Untuk Int

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

        return Convert.ToInt32(o);
    }

Pemakaian

MyIntonverter(read["indexIntValue"])

Untuk kencan

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

Pemakaian

MyDateConverter(read["indexDateValue"])

Catatan: untuk DateTime mendeklarasikan varialbe sebagai

DateTime? variable;

4

Sebagai tambahan untuk jawaban oleh marc_s, Anda dapat menggunakan metode ekstensi yang lebih umum untuk mendapatkan nilai dari SqlDataReader:

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

Saya tidak akan menyebut metode ini "SafeGet" karena jika T adalah struct, itu akan mengkonversi nol ke default non-null untuk T - tidak benar-benar aman. Mungkin "GetValueOrDefault".
Rhys Jones

@RhysJones Mengapa Anda memiliki T sebagai struct dalam kasus ini? Bahkan jika Anda melakukannya saya berpendapat bahwa default non-null dalam struct adalah perilaku yang diharapkan.
getpsyched

@RhysJones Tapi saya setuju bahwa metode ini mungkin tidak aman, perlu menangani pengecualian seperti InvalidCastException dari SqlDataReader.
getpsyched

3

Saya pikir Anda ingin menggunakan:

SqlReader.IsDBNull(indexFirstName)

2

Kami menggunakan serangkaian metode statis untuk menarik semua nilai dari pembaca data kami. Jadi dalam hal ini kami akan meneleponDBUtils.GetString(sqlreader(indexFirstName)) . Manfaat membuat metode statis / bersama adalah Anda tidak perlu melakukan pemeriksaan yang sama berulang-ulang ...

Metode statis akan berisi kode untuk memeriksa nol (lihat jawaban lain di halaman ini).


2

Anda dapat menggunakan operator kondisional:

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

Sama dengan salah satu jawaban di bawah ini, tetapi tertinggal 8 tahun!
beercohol

2

Ada banyak jawaban di sini dengan info berguna (dan beberapa info salah) tersebar, saya ingin menggabungkan semuanya.

Jawaban singkat untuk pertanyaan ini adalah untuk memeriksa DBNull - hampir semua orang setuju pada bagian ini :)

Daripada menggunakan metode pembantu untuk membaca nilai nullable per tipe data SQL metode generik memungkinkan kita untuk mengatasi ini dengan kode yang jauh lebih sedikit. Namun, Anda tidak dapat memiliki metode generik tunggal untuk tipe nilai nullable dan tipe referensi, ini dibahas panjang lebar dalam tipe Nullable sebagai parameter generik yang mungkin? dan batasan tipe generik C # untuk semua yang dapat dibatalkan .

Jadi, sebagai lanjutan dari jawaban dari @ZXX dan @getpsyched kita berakhir dengan ini, 2 metode untuk mendapatkan nilai nullable dan saya telah menambahkan 3 untuk nilai-nilai non-null (melengkapi set berdasarkan metode penamaan).

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

Saya biasanya menggunakan nama kolom, ubah ini jika Anda menggunakan indeks kolom. Berdasarkan nama-nama metode ini saya bisa tahu apakah saya mengharapkan data menjadi nullable atau tidak, cukup berguna ketika melihat kode yang ditulis sejak lama.

Kiat;

  • Tidak memiliki kolom yang dapat dibatalkan dalam basis data menghindari masalah ini. Jika Anda memiliki kontrol atas database maka kolom harus non-null secara default dan hanya nullable jika diperlukan.
  • Jangan memberikan nilai basis data dengan operator C # 'sebagai' karena jika gips salah maka diam-diam akan mengembalikan nol.
  • Menggunakan ekspresi nilai default akan mengubah basis data nol menjadi nilai bukan nol untuk jenis nilai seperti int, datetime, bit dll.

Terakhir, saat menguji metode di atas di semua tipe data SQL Server saya menemukan Anda tidak bisa langsung mendapatkan char [] dari SqlDataReader, jika Anda ingin char [] Anda harus mendapatkan string dan menggunakan ToCharArray ().


1

Saya menggunakan kode yang tercantum di bawah ini untuk menangani sel-sel nol dalam lembar Excel yang dibaca ke 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

dan / atau menggunakan operator ternary dengan penugasan:

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

ganti nilai default (bila nol) yang sesuai untuk setiap jenis properti ...


1

Metode ini tergantung pada indexFirstName yang seharusnya menjadi ordinal kolom berbasis nol.

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

Jika Anda tidak tahu indeks kolom tetapi tidak ingin memeriksa nama, Anda dapat menggunakan metode ekstensi ini sebagai gantinya:

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

Dan gunakan metode seperti ini:

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

1

Pertanyaan lama tapi mungkin seseorang masih butuh jawaban

sebenarnya saya mengatasi masalah ini seperti itu

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

sama untuk string, kembalikan saja "" alih-alih 0 karena "" adalah string kosong

jadi kamu bisa menggunakannya seperti

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

dan

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

sangat fleksibel sehingga Anda dapat memasukkan kueri apa pun untuk membaca kolom apa pun dan itu tidak akan pernah kembali dengan kesalahan


0

Ini adalah kelas pembantu yang bisa digunakan orang lain jika mereka perlu berdasarkan jawaban @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

Konversi menangani DbNull secara masuk akal.

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

Perhatikan bahwa DBNull dikonversi ke string kosong, bukan nilai nol.
Rhys Jones

-2

Anda bisa memeriksa ini juga

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

-1 Ini bukan intinya: kami menangani kasus nilai kolom nol, bukan nol atau kosongSqlDataReader
kebiruan
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.