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.