Каков наилучший способ борьбы с DBNull? - PullRequest
42 голосов
/ 26 августа 2008

У меня часто возникают проблемы с DataRows, возвращаемым с SqlDataAdapters. Когда я пытаюсь заполнить объект с помощью кода, подобного этому:

DataRow row = ds.Tables[0].Rows[0];
string value = (string)row;

Как лучше всего справиться с DBNull's в такой ситуации.

Ответы [ 14 ]

36 голосов
/ 26 августа 2008

Обнуляемые типы хороши, но только для типов, которые не обнуляются с самого начала.

Чтобы сделать тип "обнуляемым", добавьте знак вопроса, например:

int? value = 5;

Я бы также рекомендовал использовать ключевое слово "as" вместо приведения. Вы можете использовать ключевое слово «as» только для типов, допускающих значение NULL, поэтому убедитесь, что вы применяете вещи, которые уже имеют значение NULL (например, строки), или вы используете типы NULL, как указано выше. Причина этого

  1. Если тип имеет значение NULL, ключевое слово "as" возвращает null, если значение равно DBNull.
  2. Это немного быстрее, чем сотворение , хотя только в некоторых случаях . Само по себе это никогда не является достаточной причиной для использования as, но в сочетании с вышеуказанной причиной это полезно.

Я бы порекомендовал сделать что-то подобное

DataRow row = ds.Tables[0].Rows[0];
string value = row as string;

В приведенном выше случае, если row возвращается как DBNull, тогда value станет null вместо выдачи исключения. Имейте в виду, что если ваш запрос к БД изменяет возвращаемые столбцы / типы, с использованием as приведет к тому, что ваш код молча завершится с ошибкой и сделает значения простыми null вместо того, чтобы выдавать соответствующее исключение при возвращении неверных данных поэтому рекомендуется иметь тесты для проверки ваших запросов другими способами для обеспечения целостности данных по мере развития вашей кодовой базы.

21 голосов
/ 26 августа 2008

Если вы не используете пустые типы, лучше всего проверить, является ли значение столбца DBNull. Если это DBNull, тогда установите ссылку на то, что вы используете для null / empty для соответствующего типа данных.

DataRow row = ds.Tables[0].Rows[0];
string value;

if (row["fooColumn"] == DBNull.Value)
{
   value = string.Empty;
}
else 
{
   value = Convert.ToString(row["fooColumn"]);
}

Как сказал Ману, вы можете создать класс преобразования с перегруженным методом преобразования для каждого типа, чтобы вам не приходилось добавлять код в блоки if / else.

Я, однако, подчеркну, что обнуляемые типы - лучший путь, если вы можете их использовать. Причина заключается в том, что с ненулевыми типами вам придется прибегать к «магическим числам», чтобы представлять ноль. Например, если вы отображаете столбец в переменную int, как вы собираетесь представлять DBNull? Часто вы не можете использовать 0, потому что 0 имеет действительное значение в большинстве программ. Часто я вижу, как люди отображают DBNull на int.MinValue, но это также может быть проблематичным. Мой лучший совет такой:

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

Типы Nullable были созданы для решения этой проблемы. Тем не менее, если вы используете более старую версию фреймворка или работаете на кого-то, кто не использует обнуляемые типы, пример кода сработает.

8 голосов
/ 15 сентября 2008

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

Итак, предполагая обнуляемый столбец Int32 с именем «MyCol», где мы хотим вернуть -99, если столбец нулевой, но вернуть целочисленное значение, если столбец не нулевой:

return row["MyCol"] == DBNull.Value ? -99 : Convert.ToInt32(Row["MyCol"]);

Это тот же метод, что и у победителя If / Else, описанного выше - но я обнаружил, что если вы читаете несколько столбцов из устройства чтения данных, это реальный бонус, когда все строки чтения столбцов расположены под одной строкой , так как ошибки легче обнаружить:

Object.ID = DataReader["ID"] == DBNull.Value ? -99 : Convert.ToInt32(DataReader["ID"]);
Object.Name = DataReader["Name"] == DBNull.Value ? "None" : Convert.ToString(DataReader["Name"]);
Object.Price = DataReader["Price"] == DBNull.Value ? 0.0 : Convert.ToFloat(DataReader["Price"]);
8 голосов
/ 26 августа 2008

Добавить ссылку на System.Data.DataSetExtensions, которая добавляет поддержку Linq для запроса таблиц данных.

Это было бы что-то вроде:

string value = (
    from row in ds.Tables[0].Rows
    select row.Field<string>(0) ).FirstOrDefault();
5 голосов
/ 08 сентября 2008

Если у вас есть контроль над запросом, который возвращает результаты, вы можете использовать ISNULL () для возврата ненулевых значений, например:

SELECT 
  ISNULL(name,'') AS name
  ,ISNULL(age, 0) AS age
FROM 
  names

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

3 голосов
/ 16 мая 2013

Стоит отметить, что DBNull.Value.ToString() равно String.Empty

Вы можете использовать это в своих интересах:

DataRow row = ds.Tables[0].Rows[0];
string value = row["name"].ToString();

Однако, это работает только для строк, для всего остального я бы использовал способ linq или метод расширения. Для себя я написал небольшой метод расширения, который проверяет DBNull и даже выполняет приведение с помощью Convert.ChangeType(...)

int value = row.GetValueOrDefault<int>("count");
int value = row.GetValueOrDefault<int>("count", 15);
3 голосов
/ 24 марта 2009

DBNull реализует .ToString (), как и все остальное. Не нужно ничего делать. Вместо принудительного вызова, вызовите метод объекта .ToString ().

DataRow row = ds.Tables[0].Rows[0];
string value;

if (row["fooColumn"] == DBNull.Value)
{
   value = string.Empty;
}
else 
{
   value = Convert.ToString(row["fooColumn"]);
}

это становится:

DataRow row = ds.Tables[0].Rows[0];
string value = row.ToString()

DBNull.ToString () возвращает строку. Пустой

Я бы подумал, что это лучшая практика, которую вы ищете

2 голосов
/ 22 сентября 2013

Часто при работе с DataTables вам приходится иметь дело с такими случаями, когда поле строки может быть либо нулевым, либо DBNull, обычно я имею дело с этим так:

string myValue = (myDataTable.Rows[i]["MyDbNullableField"] as string) ?? string.Empty;

Оператор 'as' возвращает ноль для недопустимых приведений типа DBNull в строку и '??' возвращается термин справа от выражения, если первый равен нулю.

1 голос
/ 04 октября 2012

Вам также следует взглянуть на методы расширения. Здесь приведены некоторые примеры, связанные с этим сценарием.

Рекомендуется читать

1 голос
/ 11 февраля 2009

Брэд Абрамс опубликовал что-то связанное всего пару дней назад http://blogs.msdn.com/brada/archive/2009/02/09/framework-design-guidelines-system-dbnull.aspx

В заключение "ИЗБЕГАЙТЕ, используя System.DBNull. Вместо этого используйте Nullable."

А вот мои два цента (непроверенного кода :))

// Or if (row["fooColumn"] == DBNull.Value)
if (row.IsNull["fooColumn"])
{
   // use a null for strings and a Nullable for value types 
   // if it is a value type and null is invalid throw a 
   // InvalidOperationException here with some descriptive text. 
   // or dont check for null at all and let the cast exception below bubble  
   value = null;
}
else
{
   // do a direct cast here. dont use "as", "convert", "parse" or "tostring"
   // as all of these will swallow the case where is the incorect type.
   // (Unless it is a string in the DB and really do want to convert it)
   value = (string)row["fooColumn"];
}

И один вопрос ... По какой причине вы не используете ORM?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...