SQL Data Reader - 处理空列值

366

我正在使用 SQLdatareader 从数据库中构建 POCOs。代码运行良好,除非在数据库中遇到空值。例如,如果数据库中的 FirstName 列包含空值,将抛出异常。

employee.FirstName = sqlreader.GetString(indexFirstName);

如何在这种情况下处理空值?

2
我认为@Vijai创建的通用函数应该在这个线程中得到更多的认可。它对我来说非常有效,我不需要每次传递数据读取器对象。许多流行的答案都传递了数据读取器对象,这是不必要且沉重的。 - MSBI-Geek
30个回答

588

你需要检查 IsDBNull

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

那是您唯一可靠的检测和处理此情况的方式。

我将这些内容封装成扩展方法,并倾向于在列确实为null时返回默认值:

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

现在你可以这样调用它:

employee.FirstName = SqlReader.SafeGetString(indexFirstName);

从此你将不再担心异常或null值。


84
如果有人需要的是列名而不是索引,可以这样做:int colIndex = reader.GetOrdinal(fieldname);,然后轻松重载@marc_s的SafeGetString函数。 - ilans
7
不敢相信我居然在2019年来到这里,而且还是用VB……不过还是谢谢你,帮了大忙。 - JimmyB
也可以这样做: int ordinal = reader.GetOrdinal("col_name"); uint? val = reader.IsDBNull(ordinal) ? (uint?)null : reader.GetUInt32(ordinal); - ed22
1
大家好!我尝试将内容复制粘贴到表单中,但返回了一个错误。"扩展方法必须在非泛型静态类中定义。" - Jansen Malaggay
如果你正在SafeGetString中使用reader.GetOrindal,并且你想在循环中使用SafeGetString,那么如何在不影响性能的情况下实现呢? - AspUser7724
// 谢谢 - 一个Oracle版本... public static string SafeGetString(this OracleDataReader reader, int colIndex) { return !reader.IsDBNull(colIndex) ? reader.GetString(colIndex) : string.Empty; } // 按字段名字面意义 public static string SafeGetStringByColumnName(this OracleDataReader reader, string fieldname) { var colIndex = reader.GetOrdinal(fieldname); return SafeGetString(reader,colIndex); } - Allen

243

您应该使用as运算符与??运算符来设置默认值。 值类型需要被标记为可为空,并赋予默认值。

employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
< p > as运算符用于类型转换,包括检查DBNull。


6
如果有人将年龄列从 int 类型更改为 SQL bigint (c# long),你的代码将会无声地失败并返回 0。我认为 ZXX 的答案更可靠。 - Martin Ørding-Thomsen
6
@Chris - 你应该可以直接用-1替换default(int),意思不变。 - stevehipwell
@Stevo3000 你是正确的!我尝试了你说的方法,就在我发完帖子后它起作用了,但我忘记回到这个页面 :) - Chris
5
请注意,在这里使用 "as" 可能会隐藏索引错误。如果您意外地使用 sqlreader[indexAge] as string ?? "",则始终会得到 ""。请考虑是否真正需要 (int?)sqlreader[indexAge] ?? defaultValue,这样如果您的 SQL 发生更改,就会产生异常而不是错误值。@Stevo3000:default(int) 是 0,不是 -1。 @Chris:确保您正在使用您真正想要的那个。 - me22
@me22 - 请阅读早期的评论,关于default(int),这里没有人建议default(int)是-1。其次,你举的不使用as的例子最多只能算幼稚,当然你可能会写错任何一行代码,这是任何代码的危险之一。使用as的情况是它允许程序员编写针对可能包含DBNull(来自允许它或更有用的连接的表)的数据的代码。 - stevehipwell
显示剩余2条评论

37
employee.FirstName = sqlreader[indexFirstName] as string;

对于整数,如果你将其强制转换为可空整数类型,你可以使用GetValueOrDefault()方法。

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

或者使用空值合并运算符 (??)。

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

28

IsDbNull(int)通常比使用GetSqlDateTime等方法然后与DBNull.Value进行比较要慢得多。尝试使用这些扩展方法来操作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;
}

这样使用它们:

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

5
我发现在 System.Data.SqlTypes 类型上的显式运算符在尝试使用此代码时会引发错误... - Tetsujin no Oni
请参考 https://dev59.com/vXvZa4cB1Zd3GeqP-xnx#21024873 了解为什么有时会失败(尝试使用 Val<int> 读取 SQL int 列)。 - Rhys Jones

24

if(reader.IsDBNull(ColumnIndex)) {// logic}就像许多答案所说的那样有效。

我想提一下,如果你正在使用列名,只比较类型可能更容易。

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

这也适用于旧版本的System.Data和.NET FW。 - RaSor
只有这个对我起作用,可能是由于旧的.NET框架。 - Rush.2707

16

我认为在使用列名从数据读取器返回行时,不存在NULL列值。

如果您使用datareader["columnName"].ToString();,它将总是给您一个可以为空的值(如果需要比较,则为String.Empty)。

我会使用以下内容,不会太担心:

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

5
你可以使用 reader[FieldName] == DBNull.Value 来检查 NULL 值。 - Ralph Willgoss

15

你可以编写一个通用函数来检查 Null 值,并在其为 NULL 时包含默认值。在读取 Datareader 时调用此函数。

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

在读取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"]);
                         }

我认为这是处理任何类型的空值的最佳常见方法。我不知道为什么这个答案不受欢迎。 - MSBI-Geek

13

一种方法是检查数据库中的空值:

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

12

这个解决方案不太依赖供应商,可以与SQL、OleDB和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
将此代码直接复制并自定义到扩展类中。 - qxotk

11

受到getpsyched的答案的启发,我创建了一个通用的方法,通过列名检查列值。

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

使用方法:

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

2
我不知道你是谁,但是感谢你。这个函数节省了三个小时的工作时间。 - Ramneek Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接