Зчитувач даних SQL - обробка нульових значень стовпців


297

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

employee.FirstName = sqlreader.GetString(indexFirstName);

Який найкращий спосіб обробити нульові значення в цій ситуації?

Відповіді:


470

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


64
Якщо комусь потрібна назва стовпчика, а не індекс, ви можете зробити: int colIndex = reader.GetOrdinal(fieldname);і легко перевантажити функцію @ marc_s SafeGetString.
ilans

Не можу повірити, що я тут у 2019 році, у VB не менше .......... Хоча, чудова допомога
JimmyB

Також можна зробити так: int ordinal = reader.GetOrdinal ("col_name"); мазь? val = читач.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 на SQL 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. @Chris: Переконайтеся, що ви використовуєте той, який ви дійсно хочете.
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

@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 int).
Різ Джонс

12

Один із способів зробити це - перевірити наявність nb nulls:

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
Ralph Willgoss

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


4

як щодо створення допоміжних методів

Для струнного

private static string MyStringConverter(object o)
    {
        if (o == DBNull.Value || o == null)
            return "";

        return o.ToString();
    }

Використання

MyStringConverter(read["indexStringValue"])

Для міжнар

 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;

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".
Rhys Jones

@RhysJones Чому в такому випадку у вас буде T як структура? Навіть якщо ви це зробите, я стверджую, що ненульовий дефолт у структурі - це очікувана поведінка.
приїхав

@RhysJones Але я погоджуюся, що цей метод може бути не безпечним, він повинен обробляти винятки, такі як InvalidCastException з SqlDataReader.
приїхав

4

Впливаючи від відповіді 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")

Я не знаю, хто ти, але благословляю тебе. Ця функція зекономила протягом трьох годин роботи.
Рамнек Сінгх

3

Я думаю, ви хочете використовувати:

SqlReader.IsDBNull(indexFirstName)

2

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

Статичний метод (и) міститиме код для перевірки наявності нулей (див. Інші відповіді на цій сторінці).


2

Ви можете використовувати умовний оператор:

employee.FirstName = sqlreader["indexFirstName"] != DBNull.Value ? sqlreader[indexFirstName].ToString() : "";

Те саме, що одна з відповідей нижче, але на 8 років позаду!
beercohol

2

Тут є багато відповідей з корисною інформацією (і деякою неправильною інформацією), яку я хотів би зібрати.

Коротка відповідь на питання - це перевірити наявність DBNull - майже всі згодні з цим бітком :)

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

Отже, випливаючи з відповідей @ZXX та @getpsyched, ми закінчуємо це двома методами отримання нульових значень, і я додав 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 перетворюється на порожній рядок, а не в нульове значення.
Rhys Jones

-2

Ви можете будь-коли перевірити це

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

-1 Це не суть: ми обробляємо випадок нульового значення стовпця, а не нуля або порожньогоSqlDataReader
синюватий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.