在 ADO.NET 使用 SqlDataReader 讀取資料庫欄位時,資料庫的 NULL 值在 .NET 中對應的是 System.DBNull,而非 C# 的 null。這是初學者最常踩的坑,直接導致空值判斷失效。本文詳細說明正確判斷方式與避坑指南。
dr["Name"] == null 失效?// 錯誤寫法
while (dr.Read())
{
string name = dr["Name"].ToString(); // 若為 DBNull 會拋出例外
if (dr["Name"] == null) // 永遠為 false!
{
number += 1;
}
}
DBNull.Value(單例物件)dr["Name"] 回傳 object,實際型別為 object {System.DBNull}DBNull.Value != null,故 == null 永遠不成立ToString() 會拋出 InvalidCastExceptionif (dr["Name"] == DBNull.Value)
{
number += 1;
}
if (Convert.IsDBNull(dr["Name"]))
{
number += 1;
}
// 取得字串,若為 NULL 則回傳 string.Empty
string name = dr["Name"] == DBNull.Value ? "" : dr["Name"].ToString();
// 或使用擴充方法
public static string GetStringSafe(this IDataRecord dr, string columnName)
{
int ordinal = dr.GetOrdinal(columnName);
return dr.IsDBNull(ordinal) ? "" : dr.GetString(ordinal);
}
// 使用
string name = dr.GetStringSafe("Name");
int nullCount = 0;
string connStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=TestDB;Integrated Security=True";
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var cmd = new SqlCommand("SELECT Name, InputDate FROM TestTable", conn))
using (var dr = cmd.ExecuteReader())
{
while (dr.Read())
{
// 正確判斷 NULL
if (Convert.IsDBNull(dr["Name"]))
{
nullCount++;
continue;
}
// 安全取值
string name = dr["Name"].ToString();
DateTime inputDate = dr.GetDateTime(dr.GetOrdinal("InputDate"));
Console.WriteLine($"Name: {name}, Date: {inputDate:yyyy-MM-dd}");
}
}
}
Console.WriteLine($"Name 為 NULL 的筆數:{nullCount}");
| SQL 型別 | .NET 型別 | NULL 表示 | 安全讀取方法 |
|---|---|---|---|
| varchar/nvarchar | string | DBNull.Value | dr.IsDBNull(i) ? "" : dr.GetString(i) |
| int/bigint | int/long | DBNull.Value | dr.IsDBNull(i) ? 0 : dr.GetInt32(i) |
| datetime/date | DateTime | DBNull.Value | dr.IsDBNull(i) ? DateTime.MinValue : dr.GetDateTime(i) |
| decimal/money | decimal | DBNull.Value | dr.IsDBNull(i) ? 0m : dr.GetDecimal(i) |
| bit | bool | DBNull.Value | dr.IsDBNull(i) ? false : dr.GetBoolean(i) |
// 使用 Nullable 型別直接對應資料庫 NULL
int? age = dr.IsDBNull(dr.GetOrdinal("Age")) ? (int?)null : dr.GetInt32(dr.GetOrdinal("Age"));
DateTime? birthDate = dr.IsDBNull(dr.GetOrdinal("BirthDate")) ? (DateTime?)null : dr.GetDateTime(dr.GetOrdinal("BirthDate"));
// C# 7.0+ 模式比對
if (dr["Age"] is int ageValue) { /* 非 NULL */ }
if (dr["Age"] is DBNull) { /* 是 NULL */ }
| 錯誤情境 | 錯誤結果 | 正確做法 |
|---|---|---|
dr["Col"].ToString() 未判斷 NULL | InvalidCastException | 先判斷 Convert.IsDBNull(dr["Col"]) |
dr.GetInt32(i) 讀取 NULL 欄位 | SqlNullValueException | 先判斷 dr.IsDBNull(i) |
dr["Col"] == null 判斷 | 永遠 false,邏輯錯誤 | 改用 dr["Col"] == DBNull.Value |
| DataTable.Select 篩選 NULL | 語法錯誤 | 使用 "Col IS NULL" 或 "Col IS NOT NULL" |
資料庫 NULL ≠ C# null,這是 ADO.NET 開發的鐵律。養成「先 IsDBNull 判斷,再安全取值」的習慣,並善用 Convert.IsDBNull、DataRecordExtensions.GetFieldValue<T> 或自訂擴充方法,能徹底避免空值相關的執行期例外與邏輯錯誤。記住:DBNull.Value 是單例物件,代表「資料庫層面的無值」,而 null 代表「.NET 參考型別的無物件」,兩者語意層級完全不同。