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

我使用SQLdatareader从数据库构建poco。除非在数据库中遇到空值,否则代码可以正常工作。例如,如果数据库中的FirstName列包含空值,则抛出异常。

employee.FirstName = sqlreader.GetString(indexFirstName);

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

466763 次浏览

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

一种方法是检查db是否为null:

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

我们使用一系列静态方法从数据读取器中提取所有值。创建静态/共享方法的好处是,你不必一遍又一遍地做同样的检查…

静态方法将包含检查空值的代码(参见本页的其他答案)。

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

SELECT ISNULL(firstname, '') FROM people

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

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

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

替换每个属性类型的默认值(当为空时)…

在你尝试阅读sqlreader.IsDBNull(indexFirstName)之前检查它。

我想你会想用:

SqlReader.IsDBNull(indexFirstName)

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

对于默认值,应该将as操作符与??操作符结合使用。值类型需要被读取为空,并给出默认值。

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

as操作符处理强制转换,包括检查DBNull。

你也可以检查这个

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

当在数据读取器中使用列名返回行时,我不认为有列值。

如果你执行datareader["columnName"].ToString();,它总是会给你一个可以是空字符串的值(如果你需要比较String.Empty)。

我会使用以下方法,不会太担心:

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

此解决方案较少依赖于供应商,并与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);
}

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

if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
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();
}
employee.FirstName = sqlreader[indexFirstName] as string;

对于整数,如果转换为可空整型,则可以使用GetValueOrDefault()

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

或空合并操作符(??)。

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

这个方法依赖于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"];
}

if(reader.IsDBNull(ColumnIndex)) {// logic}工作正如许多答案所说。

我想提一下,如果你使用列名,比较类型可能会更舒服。

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

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

你可以使用条件运算符:

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

如何创建帮助方法

为字符串

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


return o.ToString();
}

使用

MyStringConverter(read["indexStringValue"])

为整数

 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声明变量为

DateTime? variable;

老问题了,但也许有人还需要一个答案

在现实生活中,我就是这样解决这个问题的

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

非常灵活,所以你可以插入任何查询读取任何列,它永远不会返回错误

作为marc_s答案的补充,你可以使用一个更通用的扩展方法从SqlDataReader中获取值:

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

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

下面是其他人可以根据@marc_s的答案使用的helper类:

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

Convert明智地处理DbNull。

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

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

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

相比使用辅助方法读取每个SQL数据类型的可空值,泛型方法允许我们用更少的代码来解决这个问题。然而,对于可空值类型和引用类型,你不能有一个单一的泛型方法,这将在中详细讨论 可空类型作为泛型参数可能吗?c#泛型类型约束。< / p >

所以,从@ZXX和@getpsyched的答案中,我们最终得到了这个,2个方法用于获取可空值,我已经添加了第三个非空值(它完成了基于方法命名的集合)。

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, 李等。< / >

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

整洁的小笑话:

    while (dataReader.Read())
{
employee.FirstName = (!dataReader.IsDBNull(dataReader.GetOrdinal("FirstName"))) ? dataReader["FirstName"].ToString() : "";
}

这些都不是我想要的:

 public static T GetFieldValueOrDefault<T>(this SqlDataReader reader, string name)
{
int index = reader.GetOrdinal(name);
T value = reader.IsDBNull(index) ? default(T) : reader.GetFieldValue<T>(index);
return value;
}

在c# 7.0中,我们可以做到:

var a = reader["ERateCode"] as string;
var b = reader["ERateLift"] as int?;
var c = reader["Id"] as int?;

如果是,它会保持空值。

我尽力从DataTable中重新实现Field方法。https://learn.microsoft.com/en-us/dotnet/api/system.data.datarowextensions.field

如果试图将DBNull.Value转换为非空类型,则会抛出。否则,它将把DBNull.Value转换为null

我还没有完全测试过。

public static T Field<T>(this SqlDataReader sqlDataReader, string columnName)
{
int columnIndex = sqlDataReader.GetOrdinal(columnName);
if (sqlDataReader.IsDBNull(columnIndex))
{
if (default(T) != null)
{
throw new InvalidCastException("Cannot convert DBNULL value to type " + typeof(T).Name);
}
else
{
return default(T);
}
}
else
{
return sqlDataReader.GetFieldValue<T>(columnIndex);
}
}

用法:

string fname = sqlDataReader.Field<string>("FirstName");
int? age = sqlDataReader.Field<int?>("Age");
int yearsOfExperience = sqlDataReader.Field<int?>("YearsEx") ?? 0;