SQL数据读取器-处理空列值


297

我正在使用SQLdatareader从数据库构建POCO。该代码有效,除非它在数据库中遇到空值。例如,如果数据库中的“名字”列包含空值,则将引发异常。

employee.FirstName = sqlreader.GetString(indexFirstName);

在这种情况下处理空值的最佳方法是什么?

Answers:


470

您需要检查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值了。


64
如果有人需要列名而不是索引,则可以执行以下操作:int colIndex = reader.GetOrdinal(fieldname);并轻松重载@marc_s的SafeGetString函数。
ilans 2015年

简直不敢相信我会在2019年出现在VB中..........尽管如此,谢谢您的大力帮助
JimmyB

也可以这样完成:int ordinal = reader.GetOrdinal(“ col_name”); int?val = reader.IsDBNull(ordinal)吗?(uint?)null:reader.GetUInt32(ordinal);
ed22

大家好!我试图复制并粘贴到表单中并返回错误。“扩展方法必须在非泛型静态类中定义。”
Jansen Malaggay

如果在SafeGetString中使用reader.GetOrindal,并且想在循环中使用SafeGetString,如何在不影响性能的情况下实现这一点?
AspUser7724

223

您应该将as运算符与??运算符结合使用以获得默认值。值类型将需要被读取为可空值并提供默认值。

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

as运营商处理的铸造,包括支票的DBNull。


6
如果有人将Age列从int更改为SQL bigint(c#long),则您的代码将通过返回0静默失败。ZXX的答案是更可靠的IMO。
2011年

我想知道您是否可以将default(int)覆盖为-1而不是0
Chris

5
@Chris-您应该能够只将default(int)替换为-1。
stevehipwell

@ Stevo3000你是正确的!我尝试了一下,并按照您发布后所说的那样进行了工作,但我忘了回到此页面:)
克里斯(Chris)

5
请注意,此处使用“ as”可能会隐藏索引错误。如果您不小心使用了sqlreader[indexAge] as string ?? "",您将永远得到""。考虑您是否真正想要(int?)sqlreader[indexAge] ?? defaultValue替代,因此,如果您的SQL更改,您将获得异常而不是错误的值。@ Stevo3000:default(int)为0,而不是-1。@克里斯:确保您使用的是您真正想要的那个。
me22 2013年

30

对于字符串,您可以简单地转换对象版本(使用数组运算符访问),并使用null字符串表示null:

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

要么

employee.FirstName = sqlreader[indexFirstName] as string;

对于整数,如果将其强制转换为可为null的int,则可以使用GetValueOrDefault()

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

或空值运算符(??)。

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

2
如您的第一个示例一样,显式强制转换不起作用。它引发了相同的错误
Musefan '17

@musefan:您的字段实际上是字符串吗?如果没有,您将得到另一个错误。这确实可行,并且示例1和示例2之间没有实际区别(除了语法)。
Gone Coding

1
@GoneCoding:是的,它是一个可为空的字符串,并且肯定是第一个在第二个起作用时引起问题的情况。我认为问题是由如何处理空值引起的。null与之类似,它们不是DBNull对象。这两个语句之间的区别是,如果不是字符串,则第一个语句将失败,而如果不是字符串,则第二个语句将仅返回null。
musefan '17

23

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类型上的显式运算符在尝试使用此代码的任何地方都
引发

有关为何有时会失败的说明,请参见stackoverflow.com/a/21024873/1508467(尝试使用Val <int>读取SQL int列)。
里斯·琼斯

12

一种方法是检查db null:

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

12

reader.IsDbNull(ColumnIndex) 可以说很多答案。

我想提一下,如果您使用列名,则仅比较类型可能会更舒适。

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

这也适用于旧版本的System.Data和.NET FW
RaSor

11

我不认为使用列名在datareader中返回行时没有NULL列值。

如果这样做datareader["columnName"].ToString();,它将始终为您提供一个可以为空字符串的值(String.Empty如果需要比较)。

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

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

4
您可以执行读取器[FieldName] == DBNull.Value,以检查是否为NULL
Ralph Willgoss

11

该解决方案与供应商的依赖性较小,可与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

8

我倾向于用适当的东西替换SELECT语句中的空值。

SELECT ISNULL(firstname, '') FROM people

在这里,我将每个null替换为空白字符串。在这种情况下,您的代码不会出错。


如果可能,请避免使用null。否则,我喜欢Sonny Boy对辅助方法的回答。
不退款

3
为什么要使用静态的独立帮助器方法?SqlDataReader上的扩展方法看起来不那么引人注目并且更直观吗?
marc_s

7

您可以编写一个泛型函数来检查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"]);
                         }


4

如何创建辅助方法

对于字符串

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

        return o.ToString();
    }

用法

MyStringConverter(read["indexStringValue"])

对于Int

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

        return Convert.ToInt32(o);
    }

用法

MyIntonverter(read["indexIntValue"])

对于日期

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

用法

MyDateConverter(read["indexDateValue"])

注意:对于DateTime,将varialbe声明为

DateTime? variable;

4

除了marc_s的答案之外,您可以使用更通用的扩展方法从SqlDataReader获取值:

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

我不会将此方法称为“ SafeGet”,因为如果T是一个结构,它将把null转换为T的非null缺省值-不是很安全。也许是“ GetValueOrDefault”。
Rhys Jones

@RhysJones在这种情况下,为什么要使用T作为结构体?即使您这样做,我也会认为结构中的非null默认值是预期的行为。
getpsyched

@RhysJones但我同意这种方法可能并不安全,它需要处理SqlDataReader中的InvalidCastException之类的异常。
getpsyched

4

通过影响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")

我不知道你是谁,但祝福你。此功能节省了三个多小时的工作。
Ramneek Singh

3

我认为您会想要使用:

SqlReader.IsDBNull(indexFirstName)

2

我们使用一系列静态方法将所有值从数据读取器中拉出。因此,在这种情况下,我们将调用DBUtils.GetString(sqlreader(indexFirstName)) 创建静态/共享方法的好处是,您不必一遍又一遍地进行相同的检查...

静态方法将包含用于检查null的代码(请参阅此页上的其他答案)。


2

您可以使用条件运算符:

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

与以下答案之一相同,但落后8年!
beercohol '17

2

这里有很多有用的信息(和一些错误的信息)的答案,我想将它们结合在一起。

这个问题的简短答案是检查DBNull-几乎每个人都同意这一点:)

通用方法不必使用辅助方法来读取每种SQL数据类型的可为空的值,而是可以使用更少的代码来解决此问题。但是,您不能为可空值类型和引用类型都使用单个通用方法,这在Nullable类型中作为通用参数进行了详细讨论 吗?C#通用类型约束,所有内容都可以为null

因此,根据来自@ZXX和@getpsyched的答案,我们最终得到了两种用于获取可为空的值的方法,并且我为非空值添加了第三个方法(它基于方法命名来完成设置)。

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

我通常使用列名,如果使用列索引,请更改它们。基于这些方法名称,我可以判断我是否期望数据为空,这在查看很久以前编写的代码时非常有用。

提示;

  • 数据库中没有可空列可以避免此问题。如果您可以控制数据库,则列默认情况下应为非空值,并且在必要时只能为空值。
  • 不要使用C#'as'运算符强制转换数据库值,因为如果强制转换错误,它将静默返回null。
  • 使用默认值表达式会将int,datetime,bit等值类型的数据库null更改为非null值。

最后,在测试所有SQL Server数据类型的上述方法时,我发现您无法直接从SqlDataReader获取char [],如果想要char [],则必须获取字符串并使用ToCharArray()。


1

我正在使用下面列出的代码来处理读入数据表的Excel工作表中的空单元格。

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

和/或使用三元运算符进行赋值:

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

替换每种属性类型的默认值(如果为null)...


1

此方法取决于indexFirstName,该索引应该是从零开始的列序号。

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

如果您不知道列索引但不想检查名称,则可以使用以下扩展方法:

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

并使用如下方法:

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

1

旧问题,但也许有人仍然需要答案

实际上,我像这样解决了这个问题

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

字符串相同,只是返回“”而不是0,因为“”为空字符串

所以你可以像

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

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

非常灵活,因此您可以插入任何查询以读取任何列,并且永远不会返回错误


0

以下是基于@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

转换明智地处理DbNull。

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

请注意,DBNull将转换为空字符串,而不是空值。
Rhys Jones

-2

你也可以检查一下

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

-1这不是重点:我们正在处理空列值的情况,而不是空值或空值的情况SqlDataReader
蓝蓝的2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.