SQL Data Reader - обработка нулевых значений столбца


298

Я использую SQLdatareader для создания POCO из базы данных. Код работает за исключением случаев, когда он встречает нулевое значение в базе данных. Например, если столбец FirstName в базе данных содержит нулевое значение, возникает исключение.

employee.FirstName = sqlreader.GetString(indexFirstName);

Каков наилучший способ обработки нулевых значений в этой ситуации?

Ответы:


471

Вам необходимо проверить 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значении.


65
Если кому-то нужно имя столбца, а не индекс, вы можете сделать это: int colIndex = reader.GetOrdinal(fieldname);и легко перегрузить SafeGetStringфункцию @ marc_s .
— ilans

Не могу поверить, что я здесь, в 2019 году, в VB не меньше .......... Спасибо, тем не менее, большая помощь
— JimmyB

Также это можно сделать следующим образом: int ordinal = reader.GetOrdinal ("col_name"); UINT? val = reader.IsDBNull (порядковый номер)? (uint?) null: reader.GetUInt32 (порядковый номер);
— ed22

Привет ребята! Я попытался скопировать и вставить в форму и вернулся с ошибкой. Msgstr "Метод расширения должен быть определен в неуниверсальном статическом классе."
— Янсен Малаггей,

Если вы используете reader.GetOrindal внутри SafeGetString и хотите использовать SafeGetString внутри цикла, как этого можно достичь без снижения производительности?
— AspUser7724

223

Вы должны использовать asоператор в сочетании с ??оператором для значений по умолчанию. Типы значений должны быть прочитаны как обнуляемые и заданы по умолчанию.

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

asОператор обрабатывает отливку , включая проверки на DBNull.


6
Если кто-то из столбца Age из int превращает int в bigint (c # long), ваш код молча завершится ошибкой, вернув 0. Ответ от ZXX является более надежным IMO.
— Мартин Эрдинг-Томсен

Интересно, можно ли переопределить значение по умолчанию (int) на -1 вместо 0
— Крис

5
@Chris - вы можете просто заменить default (int) на -1.
— Stevehipwell

@ Stevo3000 Вы правы! Я попробовал это, и это сработало, как вы сказали сразу после того, как я написал, но я забыл вернуться на эту страницу :)
— Крис,

5
Имейте в виду, что использование «как» может скрыть ошибки индекса. Если вы случайно используете sqlreader[indexAge] as string ?? "", вы всегда получите "". Подумайте, действительно ли вы хотите (int?)sqlreader[indexAge] ?? defaultValueвместо этого, поэтому, если ваш SQL изменяется, вы получаете исключения вместо неправильных значений. @ Stevo3000: по умолчанию (int) 0, а не -1. @ Крис: Убедитесь, что вы используете тот, который вы действительно хотите.
— me22

30

Для строки вы можете просто привести версию объекта (доступ осуществляется с помощью оператора массива) и получить пустую строку для нулей:

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;

2
Явное приведение, как в вашем первом примере, не работает.
— Выдает

@musefan: действительно ли ваше поле является строкой? Если нет, вы получите другую ошибку. Это работает, и это не является реальной разницей между примерами 1 и 2 (кроме синтаксиса).
— Ушел кодирование

1
@GoneCoding: Да, это пустая строка, и, безусловно, первый случай вызывает проблемы, когда работает второй. Я предполагаю, что проблема вызвана тем, как обрабатываются нулевые значения. Как в, они не, nullно вместо этого объект DBNull. Разница между этими двумя операторами заключается в том, что первый из них потерпит неудачу, если он не является строкой, тогда как второй просто вернет ноль, если это не строка.
— Musefan

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 генерируют ошибки везде, пытаясь использовать этот код ...
— Tetsujin no Oni

См. Stackoverflow.com/a/21024873/1508467 для объяснения того, почему иногда это не удается (попробуйте использовать Val <int> для чтения столбца SQL int).
— Рис Джонс

12

Один из способов сделать это - проверить наличие нулей в БД:

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

Я не думаю, что есть значение столбца NULL , когда строки возвращаются в хранилище данных, используя имя столбца.

Если вы сделаете datareader["columnName"].ToString();это, вы всегда получите значение, которое может быть пустой строкой ( String.Emptyесли вам нужно сравнить).

Я бы использовал следующее и не стал бы сильно беспокоиться:

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

4
Вы можете сделать читателя [FieldName] == DBNull.Value, чтобы проверить на NULL
— Ральф Виллгосс

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

Здесь я заменяю каждый ноль пустой строкой. Ваш код не будет выдавать ошибку в этом случае.


Если возможно, используйте это, чтобы избежать нуля. В противном случае мне нравится ответ Сонни Боя о вспомогательных методах.
— Нет возврата нет возврата

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

6

Проверьте, sqlreader.IsDBNull(indexFirstName)прежде чем пытаться прочитать.


5

Воздействуя от ответа 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")

Я не знаю кто ты, но благослови тебя. Эта функция сэкономила более трех часов работы.
— Рамнеек Сингх

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 объявите переменную как

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 является структурой, он будет преобразовывать нули в ненулевое значение по умолчанию для T - не совсем безопасно. Возможно "GetValueOrDefault".
— Рис Джонс

@RhysJones Почему в этом случае у вас будет T как структура? Даже если вы это сделаете, я бы сказал, что ненулевое значение по умолчанию в структуре - это ожидаемое поведение.
— Получил

@RhysJones Но я согласен, что этот метод может быть небезопасным, он должен обрабатывать исключения, такие как InvalidCastException из SqlDataReader.
— Getpsyched

3

Я думаю, что вы хотели бы использовать:

SqlReader.IsDBNull(indexFirstName)

2

Мы используем серию статических методов, чтобы извлечь все значения из наших считывателей данных. Так что в этом случае мы будем звонитьDBUtils.GetString(sqlreader(indexFirstName)) . Преимущество создания статических / разделяемых методов заключается в том, что вам не нужно делать одни и те же проверки снова и снова и снова ...

Статический метод (ы) будет содержать код для проверки на нулевые значения (см. Другие ответы на этой странице).



2

Здесь есть много ответов с полезной информацией (и некоторой неправильной информацией), я хотел бы собрать все это вместе.

Короткий ответ на вопрос - проверить DBNull - с этим согласны почти все :)

Вместо использования вспомогательного метода для чтения значений NULL для каждого типа данных SQL универсальный метод позволяет нам решить эту проблему с помощью гораздо меньшего количества кода. Однако у вас не может быть единого универсального метода для типов значений Nullable и ссылочных типов, это подробно обсуждается в типе Nullable как возможный универсальный параметр? и C # ограничение общего типа для всего обнуляемого .

Итак, исходя из ответов @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);
}

Я обычно использую имена столбцов, меняйте их, если вы используете индексы столбцов. Основываясь на названиях этих методов, я могу сказать, ожидаю ли я, что данные будут обнуляемыми или нет, что весьма полезно при просмотре кода, написанного давно.

Подсказки;

  • Отсутствие обнуляемых столбцов в базе данных позволяет избежать этой проблемы. Если у вас есть контроль над базой данных, столбцы должны быть ненулевыми по умолчанию и обнуляться только при необходимости.
  • Не приводите значения базы данных с помощью оператора C # «как», потому что, если приведение выполнено неправильно, оно молча вернет ноль.
  • Использование выражения значения по умолчанию изменит нулевые значения базы данных на ненулевые значения для типов значений, таких как int, datetime, bit и т. Д.

Наконец, во время тестирования вышеуказанных методов для всех типов данных SQL Server, которые я обнаружил, вы не можете напрямую получить char [] из SqlDataReader, если вам нужен 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);

замените значение по умолчанию (при нулевом значении) соответствующим образом для каждого типа свойства ...


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 преобразуется в пустую строку, а не в нулевое значение.
— Рис Джонс

-2

Вы также можете проверить это

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

-1 Дело не в этом: мы обрабатываем случай значения нулевого столбца, а не случай нулевого или пустогоSqlDataReader
— синеватый
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.