Я чувствую, что только очень немногие подходы здесь не рискуют больше всего беспокоить потенциального оператора (Марк Гравелл, Stevo3000, Ричард Салай, Нил, Даррен Коппанд), и большинство из них неоправданно сложны. Полностью осознавая, что это бесполезная микрооптимизация, позвольте мне сказать, что вы должны в основном использовать это:
1) Не считывайте значение из DataReader / DataRow дважды - так что либо кэшируйте его перед нулевыми проверками и приведением / преобразованием, либо, что еще лучше, напрямую передайте объект record[X]
в пользовательский метод расширения с соответствующей сигнатурой.
2) Чтобы выполнить вышесказанное, не используйте встроенную функцию IsDBNull
в вашем DataReader / DataRow, поскольку она вызывает record[X]
внутри, так что фактически вы будете делать это дважды.
3) Как правило, сравнение типов всегда будет медленнее сравнения значений. Просто сделай record[X] == DBNull.Value
лучше.
4) Прямое приведение будет быстрее, чем вызывать Convert
класс для преобразования, хотя, боюсь, последний будет меньше колебаться.
5) Наконец, доступ к записи по индексу, а не по имени столбца будет быстрее.
Я чувствую, что лучше подойти к Салаю, Нилу и Даррену Коппанду. Мне особенно нравится подход метода расширения Даррена Коппанда, который принимает IDataRecord
(хотя я хотел бы еще более сузить его до IDataReader
) и имя индекса / столбца.
Будьте осторожны, чтобы позвонить:
record.GetColumnValue<int?>("field");
а не
record.GetColumnValue<int>("field");
на случай, если вам нужно различить 0
и DBNull
. Например, если у вас есть нулевые значения в полях перечисления, в противном случае default(MyEnum)
рискует вернуть первое значение перечисления. Так что лучше позвони record.GetColumnValue<MyEnum?>("Field")
.
Поскольку вы читаете из DataRow
, я бы создал метод расширения для DataRow
и IDataReader
с помощью DRYing общего кода.
public static T Get<T>(this DataRow dr, int index, T defaultValue = default(T))
{
return dr[index].Get<T>(defaultValue);
}
static T Get<T>(this object obj, T defaultValue) //Private method on object.. just to use internally.
{
if (obj.IsNull())
return defaultValue;
return (T)obj;
}
public static bool IsNull<T>(this T obj) where T : class
{
return (object)obj == null || obj == DBNull.Value;
}
public static T Get<T>(this IDataReader dr, int index, T defaultValue = default(T))
{
return dr[index].Get<T>(defaultValue);
}
Так что теперь назовите это как:
record.Get<int>(1); //if DBNull should be treated as 0
record.Get<int?>(1); //if DBNull should be treated as null
record.Get<int>(1, -1); //if DBNull should be treated as a custom value, say -1
Я полагаю, что именно так и должно быть в фреймворке (вместо методов record.GetInt32
, record.GetString
и т. Д.), Во-первых, без исключений во время выполнения, что дает нам возможность обрабатывать нулевые значения.
Из моего опыта мне не повезло с одним общим методом для чтения из базы данных. Мне всегда приходилось настраивать различные типы, поэтому в долгосрочной перспективе мне приходилось писать собственные методы GetInt
, GetEnum
, GetGuid
и т. Д. Что, если вы хотите обрезать пробелы при чтении строки из БД по умолчанию или рассматривать DBNull
как пустую строку? Или, если ваша десятичная дробь должна быть усечена из всех конечных нулей. У меня были большие проблемы с типом Guid
, когда различные драйверы коннектора вели себя по-разному, в то время как базовые базы данных могли хранить их как строковые или двоичные. У меня перегрузка такая:
static T Get<T>(this object obj, T defaultValue, Func<object, T> converter)
{
if (obj.IsNull())
return defaultValue;
return converter == null ? (T)obj : converter(obj);
}
С подходом Stevo3000 я нахожу вызов немного уродливым и утомительным, и будет сложнее сделать из него обобщенную функцию.