SQL Data Reader - обработка значений Null column
Я использую SQLdatareader для создания POCOs из базы данных. Код работает, кроме случаев, когда он встречает нулевое значение в базе данных. Например, если столбец FirstName в базе данных содержит нулевое значение, генерируется исключение.
employee.FirstName = sqlreader.GetString(indexFirstName);
Каков наилучший способ обработки нулевых значений в этой ситуации?
Ответы
Ответ 1
Вам нужно проверить 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
.
Ответ 2
Вы должны использовать оператор as
в сочетании с оператором ??
для значений по умолчанию. Типы значений должны считаться нулевыми и задаваться по умолчанию.
employee.FirstName = sqlreader[indexFirstName] as string;
employee.Age = sqlreader[indexAge] as int? ?? default(int);
Оператор as
обрабатывает кастинг, включая проверку для DBNull.
Ответ 3
Для строки вы можете просто указать версию объекта (доступ к которой используется с помощью оператора массива) и завершить нулевую строку для нулей:
employee.FirstName = (string)sqlreader[indexFirstName];
или
employee.FirstName = sqlreader[indexFirstName] as string;
Для целых чисел, если вы применяете к nullable int, вы можете использовать GetValueOrDefault()
employee.Age = (sqlreader[indexAge] as int?).GetValueOrDefault();
или оператор нуль-коалесцирования (??
).
employee.Age = (sqlreader[indexAge] as int?) ?? 0;
Ответ 4
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
Один из способов сделать это - проверить нулевые значения db:
employee.FirstName = (sqlreader.IsDBNull(indexFirstName)
? ""
: sqlreader.GetString(indexFirstName));
Ответ 6
Я не думаю, что значение столбца NULL, когда строки возвращаются в datareader с использованием имени столбца.
Если вы выполняете datareader["columnName"].ToString();
, он всегда будет давать вам значение, которое может быть пустой строкой (String.Empty
, если вам нужно сравнить).
Я бы использовал следующее и не слишком беспокоился:
employee.FirstName = sqlreader["columnNameForFirstName"].ToString();
Ответ 7
reader.IsDbNull(ColumnIndex)
работает так, как говорят многие.
И я хочу упомянуть, если вы работаете с именами столбцов, просто сравнение типов может быть более удобным.
if(reader["TeacherImage"].GetType() == typeof(DBNull)) { //logic }
Ответ 8
Это решение менее зависимо от поставщика и работает с 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);
}
Ответ 9
Я стараюсь заменить нулевые значения в инструкции SELECT чем-то соответствующим.
SELECT ISNULL(firstname, '') FROM people
Здесь я заменяю каждый пустой пустой строкой. В этом случае ваш код не будет ошибочным.
Ответ 10
Проверьте sqlreader.IsDBNull(indexFirstName)
, прежде чем пытаться его прочитать.
Ответ 11
Вы можете написать функцию Generic, чтобы проверить 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"]);
}
Ответ 12
Я думаю, вы бы хотели использовать:
SqlReader.IsDBNull(indexFirstName)
Ответ 13
как насчет создания вспомогательных методов
Для строки
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;
Ответ 14
В дополнение к ответу от marc_s вы можете использовать более общий метод расширения для получения значений из SqlDataReader:
public static T SafeGet<T>(this SqlDataReader reader, int col)
{
return reader.IsDBNull(col) ? default(T) : reader.GetFieldValue<T>(col);
}
Ответ 15
Мы используем серию статических методов, чтобы вытащить все значения из наших считывателей данных. Таким образом, в этом случае мы будем называть DBUtils.GetString(sqlreader(indexFirstName))
. Преимущество создания статических/общих методов заключается в том, что вам не нужно делать одни и те же проверки снова и снова...
Статический метод будет содержать код для проверки нулей (см. другие ответы на этой странице).
Ответ 16
Вы можете использовать условный оператор:
employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";
Ответ 17
и/или использовать тернарный оператор с назначением:
employee.FirstName = rdr.IsDBNull(indexFirstName))?
String.Empty: rdr.GetString(indexFirstName);
заменить значение по умолчанию (когда null), соответствующее каждому типу свойств...
Ответ 18
Я использую приведенный ниже код для обработки нулевых ячеек на листе Excel, который читается в datatable.
if (!reader.IsDBNull(2))
{
row["Oracle"] = (string)reader[2];
}
Ответ 19
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();
}
Ответ 20
Этот метод зависит от 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"];
}
Ответ 21
Старый вопрос, но, возможно, кому-то еще нужен ответ
на самом деле я работал над этим вопросом так
Для 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;
очень гибкий, поэтому вы можете вставить любой запрос, чтобы прочитать любой столбец, и он никогда не вернется с ошибкой
Ответ 22
Используя ответ из 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")
Ответ 23
Вот вспомогательный класс, который могут использовать другие, если им нужно, основываясь на ответе @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);
}
}
Ответ 24
Конвертировать ручки DbNull разумно.
employee.FirstName = Convert.ToString(sqlreader.GetValue(indexFirstName));
Ответ 25
Здесь есть много ответов с полезной информацией (и некоторой неправильной информацией), я хотел бы собрать все это вместе.
Короткий ответ на вопрос - проверить DBNull - с этим согласны почти все :)
Вместо того, чтобы использовать вспомогательный метод для чтения значений NULL для каждого типа данных SQL, универсальный метод позволяет нам решить эту проблему с помощью гораздо меньшего количества кода. Однако у вас не может быть единого универсального метода для типов значений Nullable и ссылочных типов, это подробно обсуждается в типе Nullable как возможный универсальный параметр? и ограничение общего типа С# для всего, что можно обнулять.
Итак, исходя из ответов @ZXX и @getpsyched, мы в итоге получаем 2 метода для получения значений, допускающих значение NULL, и я добавил 3-й для ненулевых значений (он завершает набор на основе именования методов).
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);
}
Я обычно использую имена столбцов, меняйте их, если вы используете индексы столбцов. Основываясь на названиях этих методов, я могу сказать, ожидаю ли я, что данные будут обнуляемыми или нет, что весьма полезно при просмотре кода, написанного давным-давно.
Подсказки;
- Отсутствие обнуляемых столбцов в базе данных позволяет избежать этой проблемы. Если у вас есть контроль над базой данных, столбцы должны быть ненулевыми по умолчанию и обнуляться только при необходимости.
- Не приводите значения базы данных с помощью оператора С# "как", потому что, если приведение выполнено неправильно, оно молча вернет ноль.
- Использование выражения значения по умолчанию изменит нулевые значения базы данных на ненулевые значения для типов значений, таких как int, datetime, bit и т.д.
Наконец, во время тестирования вышеуказанных методов для всех типов данных SQL Server, которые я обнаружил, вы не можете напрямую получить char [] из SqlDataReader, если вам нужен char [], вам нужно будет получить строку и использовать ToCharArray().
Ответ 26
вы также можете проверить это,
if(null !=x && x.HasRows)
{ ....}