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)
{ ....}
Другие вопросы по тегам