Обработка DBNull в C #

Есть ли способ сделать это лучше / чище?

int stockvalue = 0;
if (!Convert.IsDBNull(reader["StockValue"]))
    stockvalue = (int)reader["StockValue"];

person Andreas    schedule 12.03.2010    source источник
comment
Вам также следует подумать о методах расширения. Вот несколько примеров для других возможных способов: shahanayyub.wordpress.com/2012/10/04/   -  person NeverHopeless    schedule 13.10.2012


Ответы (11)


Самый короткий (ИМХО):

int stockvalue = (reader["StockValue"] as int?) ?? 0;

Объяснение:

  • Если reader ["StockValue"] имеет тип int, будет возвращено значение и знак "??" оператор вернет результат
  • Если reader ["StockValue"] НЕ относится к типу int (например, DBNull), будет возвращено значение null, а знак "??" оператор вернет значение 0 (ноль).
person Philippe Leybaert    schedule 12.03.2010
comment
@Philippe: что произойдет, если тип столбца изменится на Double или что-то в этом роде? - person John Saunders; 12.03.2010
comment
+1 - Я бы так написал код, хотя, если допустимо нулевое значение, значение по умолчанию не требуется; int? stockvalue = reader["StockValue"] as int? - person stevehipwell; 12.03.2010
comment
@John: он вернет 0. Но независимо от того, какое решение вы выберете, если столбец меняет тип данных, вы облажались, если только вы не вызовете Convert.ToInt32 () или что-то в этом роде. - person Philippe Leybaert; 12.03.2010
comment
Но с явным приведением распаковки int x = (int)obj ваш код сломается при изменении типа столбца, что позволит вам исправить его на double x = (double)obj; @JohnSaunders правильно поднимает красный флаг, поскольку подход as int? незаметно поглотит это изменение; вы могли бы поймать его, только если бы кто-то заметил нулевое значение там, где его быть не должно. - person phoog; 24.02.2012
comment
фог совершенно прав. Если столбец не является int, как вы ожидаете, результатом всегда будет 0. Представьте, что другие пытаются прочитать этот код, чтобы узнать, почему результат всегда равен 0? Приведенное ниже решение Digicoder более гибкое и удобное для чтения. Просто используйте простую сокращенную формулировку if. - person Frug; 11.09.2012

Я справляюсь с этим так

int? stockvalue = reader["StockValue"] as int?;

Очень просто, чисто и в одну линию. Если по какой-то причине у меня абсолютно не может быть нулевого значения (что я обычно считаю плохим аргументом, поскольку я бы предпочел знать, имеет ли значение значение или оно было унифицировано для примитивного типа), я бы сделал:

int stockvalue = (reader["StockValue"] as int?).GetValueOrDefault(-1);
person Chris Marisic    schedule 12.03.2010
comment
Ах, у меня возникла исключительная ситуация с использованием (int?) reader["ColumnName"], и я начал искать правильный способ сделать это. Это следует принять как ответ. - person Anlo; 28.03.2013
comment
Отлично. Это также сохраняет состояние DBNull на тот случай, если кому-то будет интересно это узнать. Некоторые другие решения здесь отбрасывают это. - person Jonas; 18.02.2014

Несколько дней назад я написал метод расширения. Используя его, вы могли просто:

int? stockvalue = reader.GetValue<int?>("StockValue");

Вот метод расширения (измените в соответствии с вашими потребностями):

public static class ReaderHelper
{
    public static bool IsNullableType(Type valueType)
    {
        return (valueType.IsGenericType &&
            valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
    }

    public static T GetValue<T>(this IDataReader reader, string columnName)
    {
        object value = reader[columnName];
        Type valueType = typeof(T);
        if (value != DBNull.Value)
        {
            if (!IsNullableType(valueType))
            {
                return (T)Convert.ChangeType(value, valueType);
            }
            else
            {
                NullableConverter nc = new NullableConverter(valueType);
                return (T)Convert.ChangeType(value, nc.UnderlyingType);
            }
        }
        return default(T);
    }
}
person Vivek    schedule 12.03.2010
comment
Мне очень нравится C #, и у меня есть код, похожий на этот, в нескольких местах, но он абсолютно отсталый, нам когда-либо понадобится писать такой код. Я надеюсь, что с дальнейшим развитием DLR настанет день, когда нам никогда не понадобится писать такой код, чтобы вести борьбу за типы. - person Chris Marisic; 26.05.2011

int? stockvalue = (int?)(!Convert.IsDBNull(result) ? result : null);

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

person Digicoder    schedule 12.03.2010
comment
Считаю это решение наиболее понятным и гибким. Я выбрал что-то почти идентичное, но не допускающее нулей: Int32 aNum = Convert.IsDBNull (row [stockvalue])? 0: Convert.ToInt32 (строка [значение запаса]) - person Frug; 11.09.2012

Да, вы можете использовать int?. Таким образом, вы можете иметь значение по умолчанию null вместо 0. Поскольку результатом stockvalue потенциально может быть 0, не возникает путаницы относительно того, была ли база данных нулевой или нулевой. Например, как это (предварительно обнуляемое), у нас была инициализация по умолчанию -1, чтобы представить, что значение не было присвоено. Лично я подумал, что это немного опасно, потому что, если вы забудете установить его на -1, возникнет проблема с повреждением данных, которую может быть очень сложно отследить.

http://msdn.microsoft.com/en-us/library/2cf62fcy(VS.80).aspx

int? stockvalue = null;

if (!Convert.IsDBNull(reader["StockValue"]))
    stockvalue = (int)reader["StockValue"];

//Then you can check 

if(stockValue.HasValue)
{
  // do something here.
}
person kemiller2002    schedule 12.03.2010
comment
+1 для использования правильного типа данных. Однако IIRC, DBNull все еще нужно преобразовать в null с небольшой проверкой. - person Jon Seigel; 12.03.2010
comment
использовать int? как? Простое преобразование reader [StockValue] в (int?) Вызовет исключение, когда reader [StockValue] будет DBNull. - person Philippe Leybaert; 12.03.2010
comment
вы можете использовать int? так что вам не нужно использовать инициализацию по умолчанию 0. - person kemiller2002; 12.03.2010

Хотя ссылаться на reader["StockValue"] удобно, это не очень эффективно. Он также не является строго типизированным, поскольку возвращает тип object.

Вместо этого в своем коде сделайте что-то вроде этого:

int stockValueOrdinal = reader.GetOrdinal("StockValue");
int? stockValue = reader.IsDbNull(stockValueOrdinal) ?
    null : 
    reader.GetInt32(stockValueOrdinal);

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

person John Saunders    schedule 12.03.2010
comment
@John: в своем комментарии к моему ответу вы спросили меня, что, если столбец изменится на двойной? Код, который вы здесь предоставили, вызовет исключение, если столбец содержит значение, отличное от типа int. (также небольшая деталь: в считывателе данных нет метода .GetInt (). Он должен быть .GetInt32 ()) - person Philippe Leybaert; 12.03.2010
comment
@Philippe: выброс исключения - это в точности то, что должен делать код, если тип столбца изменяется, а код - нет. Возвращать ноль, когда что-то серьезное не так, - очень плохая идея. - person John Saunders; 12.03.2010

Вот один способ.

int stockvalue = Convert.IsDbNull(reader["StockValue"]) ? 0 : (int)reader["StockValue"];

Вы также можете использовать TryParse

int stockvalue = 0
Int32.TryParse(reader["StockValue"].ToString(), out stockvalue);

Сообщите нам, какой способ работает для вас

person sidney.andrews    schedule 12.03.2010
comment
Я не возражаю против голосов против, просто оставьте комментарий, в котором рассказывается, как я могу улучшить этот ответ. - person sidney.andrews; 12.03.2010
comment
Здесь определенно нет причин для DV, так как ваш ответ - правильное решение. - person Chris Marisic; 12.03.2010
comment
Да, ничего плохого в этом нет. Я проголосовал за вас, чтобы сбалансировать это. :-) - person Brian Scott; 12.03.2010
comment
Я не голосовал против, но это очень неэффективный фрагмент кода. Ваша первая строка дважды вызывает индексатор reader []. Во втором используются очень неэффективные преобразования строк, в которых нет необходимости. - person Philippe Leybaert; 12.03.2010
comment
Хороший момент, я обычно не использую никаких устройств чтения данных в своих приложениях. Вместо этого выбирайте объектно-ориентированное представление внутренних данных. - person sidney.andrews; 12.03.2010

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

Но я бы не назвал это «чище», если только вы не сможете последовательно использовать эту форму в своем коде, поскольку вы потеряете информацию, вернув из БД «0» вместо NULL.

person Community    schedule 12.03.2010
comment
Аргумент о потере информации имеет смысл. В приложении базы данных значение 0 отличается от NULL. Один означает, что у него нет никакого значения, а один - это значение, которое случайно оказывается равным нулю. - person sidney.andrews; 12.03.2010

используйте Nullable<int> тип ..._ 2_ для краткости

person Rich    schedule 12.03.2010

Не совсем. Вы можете инкапсулировать это в методе:

public int getDBIntValue(object value, int defaultValue) {
  if (!Convert.IsDBNull(value)) {
    return (int)value;
  }
  else {
    return defaultValue;
  }

И назовите это так:

stockVaue = getDBIntVaue(reader["StockValue"], 0);

Или вы можете использовать coalesce в своем запросе, чтобы возвращаемое значение было ненулевым.

Edit - исправлены ошибки тупого кода на основе полученных комментариев.

person Ray    schedule 12.03.2010
comment
(Я не отрицал вас, но) Во-первых, вы передали читателю [StockValue] в параметре значения, но затем проигнорировали параметр значения внутри функции и вместо этого использовали читатель [StockValue], поэтому он не будет компилироваться. - person Adam V; 12.03.2010
comment
@Ray: Ваш getDBIntValue всегда будет пытаться извлечь int из reader["StockValue"], независимо от того, что передано. У вас также есть ключевое слово default в качестве одного из ваших параметров, который не будет компилироваться (вам нужно будет сделать это @default или - - лучше - просто смени название). - person Dan Tao; 12.03.2010
comment
@John - Я исправил сделанную мной ошибку копирования и вставки. Тем не менее, если вы не знаете, что я не собираюсь вам рассказывать, это не помогает мне (или кому-либо еще, читающим эти комментарии) учиться. - person Ray; 12.03.2010
comment
@Dan: по-видимому, он понимает, что он всегда будет иметь int, отсюда и название getDB Int Vaue (кстати, Рэй, пожалуйста, добавьте недостающую букву l. Орфографические ошибки меня беспокоят). - person Adam V; 12.03.2010
comment
Это научит меня просто печатать и публиковать, не проверяя свой код - извините за глупые ошибки - я думаю, что теперь это исправлено. - person Ray; 12.03.2010
comment
@adam - орфография исправлена ​​- моя клавиша "L" сегодня залипает. И да, вы должны знать, что получаете int. Если бы кто-то использовал эти типы служебных методов, у вас были бы getDBString, getDBByte и т. Д. - person Ray; 12.03.2010
comment
@Adam: Изначально (до того, как он исправил код) метод назывался Convert.IsDBNull(reader["StockValue"]) независимо от переданного параметра. Таким образом, он всегда будет смотреть на reader["StockValue"], а не на любой другой столбец в любой другой таблице. Это было то, что я указывал, а не то, что он возвращает int (что, как вы правильно указываете, должно быть очевидным). - person Dan Tao; 12.03.2010

В моем проекте есть два следующих метода расширения:

    public static T GetValueSafe<T>(this IDataReader dataReader, string columnName, Func<IDataReader, int, T> valueExtractor)
        where T : class 
    {
        T value;
        if (dataReader.TryGetValueSafe(columnName, valueExtractor, out value))
        {
            return value;
        }

        return null;
    }

    public static bool TryGetValueSafe<T>(this IDataReader dataReader, string columnName, Func<IDataReader, int, T> valueExtractor, out T value)
    {
        int ordinal = dataReader.GetOrdinal(columnName);

        if (!dataReader.IsDBNull(ordinal))
        {
            // Get value.
            value = valueExtractor.Invoke(dataReader, ordinal);

            return true;
        }

        value = default(T);
        return false;
    }

Использование может быть таким:

string companyName = dataReader.GetValueSafe("CompanyName", (reader, ordinal) => reader.GetString(ordinal));
person Andrew Bezzub    schedule 12.03.2010
comment
Интересно, но очень многословно, я создал похожий класс, когда много работал с DataReaders, но сделал его намного более кратким, так как он работает как reader.Get<int>("ReaderField") и работает с использованием метода ChangeType. - person Chris Marisic; 12.03.2010
comment
Хорошая идея +1, но я думаю, что моя реализация будет немного быстрее. - person Andrew Bezzub; 12.03.2010