Самый эффективный способ проверить DBNull и назначить переменную? - PullRequest
149 голосов
/ 21 октября 2008

Этот вопрос иногда возникает, но я не нашел удовлетворительного ответа.

Типичный шаблон (строка DataRow ):

 if (row["value"] != DBNull.Value)
 {
      someObject.Member = row["value"];
 }

Мой первый вопрос, который является более эффективным (я перевернул условие):

  row["value"] == DBNull.Value; // Or
  row["value"] is DBNull; // Or
  row["value"].GetType() == typeof(DBNull) // Or... any suggestions?

Это указывает, что .GetType () должен быть быстрее, но, возможно, компилятор знает несколько хитростей, которые я не знаю?

Второй вопрос: стоит ли кэшировать значение строки ["value"] или компилятор все равно оптимизирует индексатор?

Например:

  object valueHolder;
  if (DBNull.Value == (valueHolder = row["value"])) {}

Примечания:

  1. строка ["значение"] существует.
  2. Я не знаю индекс столбца столбца (отсюда и поиск по имени столбца).
  3. Я спрашиваю конкретно о проверке DBNull, а затем о назначении (не о преждевременной оптимизации и т. Д.).

Я протестировал несколько сценариев (время в секундах, 10 000 000 испытаний):

row["value"] == DBNull.Value: 00:00:01.5478995
row["value"] is DBNull: 00:00:01.6306578
row["value"].GetType() == typeof(DBNull): 00:00:02.0138757

Object.ReferenceEquals имеет ту же производительность, что и "=="

Самый интересный результат? Если вы не соответствуете имени столбца в каждом конкретном случае (например, «Значение» вместо «значение», это займет примерно в десять раз больше (для строки):

row["Value"] == DBNull.Value: 00:00:12.2792374

Мораль этой истории в том, что если вы не можете найти столбец по его индексу, убедитесь, что имя столбца, которое вы передаете в индексатор, точно совпадает с именем DataColumn.

Кэширование значения также представляется почти в два раза быстрее :

No Caching: 00:00:03.0996622
With Caching: 00:00:01.5659920

Таким образом, наиболее эффективный метод кажется следующим:

 object temp;
 string variable;
 if (DBNull.Value != (temp = row["value"]))
 {
      variable = temp.ToString();
 }

Ответы [ 15 ]

4 голосов
/ 26 ноября 2008

Я сделал нечто подобное с методами расширения. Вот мой код:

public static class DataExtensions
{
    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <typeparam name="T">The type of the data stored in the record</typeparam>
    /// <param name="record">The record.</param>
    /// <param name="columnName">Name of the column.</param>
    /// <returns></returns>
    public static T GetColumnValue<T>(this IDataRecord record, string columnName)
    {
        return GetColumnValue<T>(record, columnName, default(T));
    }

    /// <summary>
    /// Gets the value.
    /// </summary>
    /// <typeparam name="T">The type of the data stored in the record</typeparam>
    /// <param name="record">The record.</param>
    /// <param name="columnName">Name of the column.</param>
    /// <param name="defaultValue">The value to return if the column contains a <value>DBNull.Value</value> value.</param>
    /// <returns></returns>
    public static T GetColumnValue<T>(this IDataRecord record, string columnName, T defaultValue)
    {
        object value = record[columnName];
        if (value == null || value == DBNull.Value)
        {
            return defaultValue;
        }
        else
        {
            return (T)value;
        }
    }
}

Чтобы использовать его, вы должны сделать что-то вроде

int number = record.GetColumnValue<int>("Number",0)
4 голосов
/ 25 ноября 2008

Вот так я справляюсь с чтением из DataRows

///<summary>
/// Handles operations for Enumerations
///</summary>
public static class DataRowUserExtensions
{
    /// <summary>
    /// Gets the specified data row.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="dataRow">The data row.</param>
    /// <param name="key">The key.</param>
    /// <returns></returns>
    public static T Get<T>(this DataRow dataRow, string key)
    {
        return (T) ChangeTypeTo<T>(dataRow[key]);
    }

    private static object ChangeTypeTo<T>(this object value)
    {
        Type underlyingType = typeof (T);
        if (underlyingType == null)
            throw new ArgumentNullException("value");

        if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition().Equals(typeof (Nullable<>)))
        {
            if (value == null)
                return null;
            var converter = new NullableConverter(underlyingType);
            underlyingType = converter.UnderlyingType;
        }

        // Try changing to Guid  
        if (underlyingType == typeof (Guid))
        {
            try
            {
                return new Guid(value.ToString());
            }
            catch

            {
                return null;
            }
        }
        return Convert.ChangeType(value, underlyingType);
    }
}

Пример использования:

if (dbRow.Get<int>("Type") == 1)
{
    newNode = new TreeViewNode
                  {
                      ToolTip = dbRow.Get<string>("Name"),
                      Text = (dbRow.Get<string>("Name").Length > 25 ? dbRow.Get<string>("Name").Substring(0, 25) + "..." : dbRow.Get<string>("Name")),
                      ImageUrl = "file.gif",
                      ID = dbRow.Get<string>("ReportPath"),
                      Value = dbRow.Get<string>("ReportDescription").Replace("'", "\'"),
                      NavigateUrl = ("?ReportType=" + dbRow.Get<string>("ReportPath"))
                  };
}

Переходит к Монстры получили мой .Net для кода ChageTypeTo.

4 голосов
/ 21 октября 2008

Я всегда использую:

if (row["value"] != DBNull.Value)
  someObject.Member = row["value"];

Нашел его кратким и полным.

3 голосов
/ 05 мая 2011
public static class DBH
{
    /// <summary>
    /// Return default(T) if supplied with DBNull.Value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static T Get<T>(object value)
    {   
        return value == DBNull.Value ? default(T) : (T)value;
    }
}

используйте вот так

DBH.Get<String>(itemRow["MyField"])
3 голосов
/ 20 мая 2010

У меня есть IsDBNull в программе, которая читает много данных из базы данных. С IsDBNull он загружает данные примерно за 20 секунд. Без IsDBNull, около 1 секунды.

Так что я думаю, что лучше использовать:

public String TryGetString(SqlDataReader sqlReader, int row)
{
    String res = "";
    try
    {
        res = sqlReader.GetString(row);
    }
    catch (Exception)
    { 
    }
    return res;
}
...