DataReader - ординалы жесткого кода?

При возврате данных из DataReader я обычно использовал бы порядковый номер ссылки DataReader для захвата соответствующего столбца:

if (dr.HasRows)         
   Console.WriteLine(dr[0].ToString());

или

if (dr.HasRows)         
   Console.WriteLine(dr.GetString(0));

или

if (dr.HasRows)         
   Console.WriteLine((string)dr[0]);

Я всегда делал это, потому что на ранней стадии мне советовали, что использование dr["ColumnName"] или более элегантный способ индексирования приводит к поражению производительности.

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

Какой самый надежный способ вернуть данные из DataReader?

Ответы

Ответ 1

В этой ситуации можно утверждать обе стороны. Как уже указывалось другими, использование имени более читаемо и не будет прерываться, если кто-то изменит порядок столбцов в базовой базе данных. Но можно также утверждать, что использование ординала имеет то преимущество, что оно не нарушается, если кто-то изменяет имя столбца в базовой базе данных. Однако я предпочитаю прежний аргумент и думаю, что аргумент удобочитаемости для имен столбцов превосходит второй аргумент вообще. И дополнительный аргумент для имен состоит в том, что он может "самоопределять" ошибки. Если кто-то меняет имя поля, тогда код имеет больше шансов сломаться, а не иметь тонкую ошибку появления, когда он читает неправильное поле.

Кажется очевидным, но, возможно, стоит упомянуть пример использования, который имеет как ошибку самоопределения, так и производительность ординалов. Если вы явно укажете список SELECT в SQL, то использование ординалов не будет проблемой, потому что оператор в коде гарантирует порядок:

SELECT name, address, phone from mytable

В этом случае было бы достаточно безопасно использовать ординалы для доступа к данным. Неважно, кто-то перемещает поля в таблице. И если кто-то меняет имя, то оператор SQL вызывает ошибку при ее запуске.

И один последний момент. Я просто проверил тест на провайдера, которому я помог написать. Тест прочитал 1 миллион строк и получил доступ к полю "lastname" на каждой записи (по сравнению с значением). Использование rdr["lastname"] заняло 3301 миллисекунду для обработки, тогда как rdr.GetString(1) заняло 2640 миллисекунд (приблизительно 25% ускорения). В этом конкретном провайдере поиск имени использует сортированный поиск, чтобы преобразовать имя в порядковый номер.

Ответ 2

Поиск имени строки намного дороже, чем порядковый вызов, но более удобный и менее "хрупкий", чем жесткое кодирование ординалов. Так вот, как я это делал. Это лучшее из обоих миров. Я не должен помнить порядковые значения или заботиться, если порядок столбцов меняется, но я получаю преимущества использования ординалов.

var dr = command.ExecuteQuery();
if (dr.HasRows)
{
    //Get your ordinals here, before you run through the reader
    int ordinalColumn1 = dr.GetOrdinal("Column1");
    int ordinalColumn2 = dr.GetOrdinal("Column2");
    int ordinalColumn3 = dr.GetOrdinal("Column3");

    while(dr.Read())
    {
        // now access your columns by ordinal inside the Read loop. 
        //This is faster than doing a string column name lookup every time.
        Console.WriteLine("Column1 = " + dr.GetString(ordinalColumn1);
        Console.WriteLine("Column2 = " + dr.GetString(ordinalColumn2);
        Console.WriteLine("Column3 = " + dr.GetString(ordinalColumn3);
    }
}

Примечание: это действительно имеет смысл для читателей, которых вы ожидаете иметь достойное количество строк. GetOrdinal() звонки являются дополнительными и оплачиваются только в том случае, если ваша общая экономия при вызове GetString(int ordinalNumber) в цикле больше чем стоимость вызова GetOrdinal.

Изменить: пропустил вторую часть этого вопроса. Что касается значений DBNull, я начал писать методы расширения, которые имеют дело с этой возможностью. Пример: dr.GetDatetimeSafely() В рамках этих методов расширения вы можете делать все, что вам нужно, чтобы убедиться, что вы вернете ожидаемое значение.

Ответ 3

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

Ответ 4

Индексирование считывателя данных по названию несколько дороже. Для этого есть две основные причины.

  • Типичные реализации хранят информацию о поле в структуре данных, которая использует численное индексирование. Необходимо выполнить операцию сопоставления, чтобы транспонировать имя в число.
  • Некоторые реализации будут выполнять двухпроходный поиск по имени. Первый проход пытается сопоставить имена полей с включенной чувствительностью к регистру. Если этот проход терпит неудачу, второй проход начинается с отключенной чувствительности к регистру.

Однако в большинстве случаев штраф за производительность, возникающий при поиске поля по имени, затмевается количеством времени, которое требуется, чтобы база данных выполнила команду. Не допускайте, чтобы штраф за исполнение определял ваш выбор между именем и числовым индексированием.

Несмотря на небольшое снижение производительности, я обычно выбираю индексацию имени по двум причинам.

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

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

Ответ 5

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

Для каждого из полей вам нужно вручную проверить наличие нулей.

var dr = command.ExecuteQuery();

if (dr.HasRows) {
    var customer = new Customer();
    // I'm assuming that you know each field position returned from your query.
    // Where comes the importance to write all of your selected fields and not just "*" or "ALL".
    customer.Id = dr[0] == null || dr[0] == DBNull.Value ? null : Convert.ToInt32(dr[0]);
    ...
}

В дополнение к этому он позволит вам использовать отражение и сделать этот метод GetData() более общим, предоставляя typeof (T) и получая правильный конструктор для правильного типа. Связывание с порядком каждого столбца - это единственное, чего можно избежать, но в этом случае оно становится достойным.

Ответ 6

Проблема с порядковым номером заключается в изменении порядка столбцов, и вы вынуждены модифицировать потребительский код для DataReader, в отличие от имен столбцов.

Я не думаю, что при использовании имен ординалов или столбцов вы получаете прирост производительности, это больше подходит для наилучшей практики и стандартов кодирования, а также для обслуживания кода действительно