SQL Data Reader - обработка нулевых значений столбца
Я использую SQLdatareader для создания POCO из базы данных. Код работает за исключением случаев, когда он встречает нулевое значение в базе данных. Например, если столбец FirstName в базе данных содержит нулевое значение, возникает исключение.
employee.FirstName = sqlreader.GetString(indexFirstName);
Каков наилучший способ обработки нулевых значений в этой ситуации?
30 ответов
Вам нужно проверить 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
значение снова.
Вы должны использовать as
оператор в сочетании с ??
оператор для значений по умолчанию. Типы значений должны быть прочитаны как обнуляемые и заданы по умолчанию.
employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
as
оператор обрабатывает приведение, включая проверку для DBNull.
Для строки вы можете просто привести версию объекта (доступ к которой осуществляется с помощью оператора массива) и получить пустую строку для нулей:
employee.FirstName = (string)sqlreader[indexFirstName];
или же
employee.FirstName = sqlreader[indexFirstName] as string;
Для целых чисел, если вы приводите к обнуляемому int, вы можете использовать GetValueOrDefault()
employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();
или нуль-коалесцирующий оператор (??
).
employee.Age = (sqlreader[indexAge] as int?) ?? 0;
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]);
reader.IsDbNull(ColumnIndex)
работает так много ответов говорит.
И я хочу упомянуть, если вы работаете с именами столбцов, просто сравнение типов может быть более удобным.
if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
Я не думаю, что есть значение столбца NULL, когда строки возвращаются в хранилище данных, используя имя столбца.
Если вы делаете datareader["columnName"].ToString();
это всегда даст вам значение, которое может быть пустой строкой (String.Empty
если нужно сравнить).
Я бы использовал следующее и не стал бы сильно беспокоиться:
employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
Вы можете написать универсальную функцию для проверки 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.IsDBNull(indexFirstName)
? ""
: sqlreader.GetString(indexFirstName));
Это решение менее зависит от поставщика и работает с 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);
}
Опираясь на ответ 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")
Я склоняюсь к тому, чтобы заменить пустые значения в операторе SELECT чем-то подходящим.
SELECT ISNULL(firstname, '') FROM people
Здесь я заменяю каждый ноль пустой строкой. Ваш код не будет выдавать ошибку в этом случае.
Проверьте sqlreader.IsDBNull(indexFirstName)
прежде чем пытаться это прочитать.
В дополнение к ответу от marc_s вы можете использовать более общий метод расширения для получения значений из SqlDataReader:
public static T SafeGet<T>(this SqlDataReader reader, int col)
{
return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
}
Как насчет создания вспомогательных методов
Для строки
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;
Ничего из этого я не совсем хотел:
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;
}
Я думаю, что вы хотели бы использовать:
SqlReader.IsDBNull(indexFirstName)
Вы можете использовать условный оператор:
employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
Здесь много ответов с полезной информацией (и некоторой неверной информацией), я бы хотел собрать все это воедино.
Короткий ответ на вопрос - проверить DBNull - с этим согласны почти все:)
Вместо того, чтобы использовать вспомогательный метод для чтения значений, допускающих значение NULL, для каждого типа данных SQL, общий метод позволяет нам решить эту проблему с помощью гораздо меньшего количества кода. Однако у вас не может быть единого универсального метода как для типов значений, допускающих значение NULL, так и для ссылочных типов, это подробно обсуждается в типе Nullable как возможный универсальный параметр? и ограничение универсального типа C# для всего, что допускает значение NULL.
Итак, следуя ответам @ZXX и @getpsyched, мы получаем два метода для получения значений, допускающих значение NULL, и я добавил третий для значений, отличных от NULL (он завершает набор на основе именования методов).
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);
}
Я обычно использую имена столбцов, измените их, если вы используете индексы столбцов. Основываясь на этих именах методов, я могу сказать, ожидаю ли я, что данные будут иметь значение NULL или нет, что весьма полезно при просмотре кода, написанного давным-давно.
Подсказки;
- Отсутствие в базе данных столбцов, допускающих значение NULL, позволяет избежать этой проблемы. Если у вас есть контроль над базой данных, тогда столбцы не должны иметь значение NULL по умолчанию и допускать значение NULL только при необходимости.
- Не приводите значения базы данных с помощью оператора C# as, потому что, если приведение неверно, оно автоматически вернет null.
- Использование выражения значения по умолчанию изменит пустые значения базы данных на ненулевые значения для таких типов значений, как int, datetime, bit и т. Д.
Наконец, при тестировании вышеуказанных методов для всех типов данных SQL Server я обнаружил, что вы не можете напрямую получить char[] из SqlDataReader, если вам нужен char[], вам нужно будет получить строку и использовать ToCharArray().
Мы используем серию статических методов для извлечения всех значений из наших считывателей данных. Так что в этом случае мы будем звонить DBUtils.GetString(sqlreader(indexFirstName))
Преимущество создания статических / разделяемых методов заключается в том, что вам не нужно делать одни и те же проверки снова и снова...
Статический метод (ы) будет содержать код для проверки на нулевые значения (см. Другие ответы на этой странице).
Старый вопрос, но, возможно, кому-то еще нужен ответ
на самом деле я работал над этим вопросом так
Для 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;
очень гибкий, поэтому вы можете вставить любой запрос, чтобы прочитать любой столбец, и он никогда не вернется с ошибкой
в С # 7.0 мы можем:
var a = reader["ERateCode"] as string;
var b = reader["ERateLift"] as int?;
var c = reader["Id"] as int?;
поэтому он сохранит нулевое значение, если это так.
Вот вспомогательный класс, который могут использовать другие, если им нужно, основываясь на ответе @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);
}
}
Я приложил все усилия, чтобы заново реализовать
Field
метод от
DataTable
. https://docs.microsoft.com/en-us/dotnet/api/system.data.datarowextensions.field
Он сработает, если вы попытаетесь преобразовать a в тип, не допускающий значения NULL. В противном случае он преобразует
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;
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 = rdr.IsDBNull(indexFirstName))?
String.Empty: rdr.GetString(indexFirstName);
замените значение по умолчанию (при нулевом значении) соответствующим образом для каждого типа свойства...
Этот метод зависит от 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"];
}
Я использую приведенный ниже код для обработки пустых ячеек на листе Excel, который считывается в таблицу данных.
if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
Аккуратный однострочный:
while (dataReader.Read())
{
employee.FirstName = (!dataReader.IsDBNull(dataReader.GetOrdinal("FirstName"))) ? dataReader["FirstName"].ToString() : "";
}
Конвертировать ручки DbNull разумно.
employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
Вы также можете проверить это
if(null !=x && x.HasRows)
{ ....}