2014-01-15 12 views
1

Я трачу много времени на запрос базы данных, а затем на создание коллекций объектов из запроса. Для повышения производительности я предпочитаю использовать DataReader и код выглядит примерно так:Каков наиболее эффективный способ извлечения данных из DataReader?

while(rdr.Read()){ 
    var myObj = new myObj(); 

    myObj.Id = Int32.Parse(rdr["Id"].ToString(); 

    //more populating of myObj from rdr 

    myObj.Created = (DateTime)rdr["Created"]; 
} 

Для объектов, таких как DateTime я просто бросил RDR значения требуемого класса, но это не может быть сделано для типов значений, как int следовательно (ИМХО) трудоемкая ToString() с последующим Int.Parse(...)

конечно, есть альтернатива:

myObj.Id = rdr.GetInt32(rdr.GetOrdinal("Id")); 

который выглядит чище и не включает в себя вызов ToString().

коллега и я обсуждали это сегодня - он предполагает, что доступ к rdr дважды в приведенном выше коде может быть менее эффективным, чем делать это мой старый SKOOL путь - кто-то может подтвердить или опровергнуть это, и предполагают, что из выше является best способ делать такие вещи? Я бы особенно приветствовал ответы от @JonSkeet ;-)

ответ

3

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

int idIdx = rdr.GetOrdinal("Id"); 
int createdIdx = rdr.GetOrdinal("Created"); 

while(rdr.Read()) 
{ 
    var myObj = new myObj(); 

    myObj.Id = rdr.GetFieldValue<int>(idIdx); 

    //more populating of myObj from rdr 

    myObj.Created = rdr.GetFieldValue<DateTime>(createdIdx); 
} 
+0

Это выглядит здорово, я никогда не смотрел на GetFieldValue раньше, благодаря очень действительно :-D – 5arx

+1

Если вы беспокоитесь о производительности, является альтернатива выше - обернуть IDataReader в DbEnumerator. DbEnumerator не очень хорошо известен и в основном предназначен для поддержки привязки данных, но он кэширует поиск имени поля в Hashtable и обеспечивает аналогичное повышение производительности выше для больших наборов результатов, сохраняя при этом код чище. – Joe

1

Это имеет смысл для читателя. Является ли это наиболее «эффективным» (вы буквально называете две функции вместо одной, это не будет столь же значительным, как кастинг, а затем вызов функции). В идеале вы должны пойти с вариантом, который выглядит более читаемым, если это не повредит вашей работе.

var ordinal = rdr.GetOrdinal("Id"); 
var id = rdr.GetInt32(ordinal); 
myObj.Id = id; 
1

На самом деле есть различия в производительности в том, как вы используете SqlDataReader, но они где-то в другом месте. А именно метод ExecuteReader принимает CommandBehavior.SequentialAccess:

Обеспечивает способ для DataReader для обработки строк, содержащих столбцы с большими двоичными значениями. Вместо того, чтобы загружать всю строку, SequentialAccess позволяет DataReader загружать данные в виде потока. Затем вы можете использовать метод GetBytes или GetChars, чтобы указать местоположение байта, чтобы начать операцию чтения, и ограниченный размер буфера для возвращаемых данных. Когда вы указываете SequentialAccess, вам необходимо читать из столбцов в том порядке, в котором они возвращены, хотя вам не обязательно читать каждый столбец. После того, как вы прочитали данные о местоположении в возвращаемом потоке данных, данные в этом месте или до этого места больше не могут быть прочитаны из DataReader. При использовании OleDbDataReader вы можете перечитать текущее значение столбца до его прочтения. При использовании SqlDataReader вы можете читать значение столбца только один раз.

Если вы не использовать большие двоичные значения, то это делает очень мало разницы. Получение строки и разбора субоптимально, правда, лучше получить значение с rdr.SqlInt32(column), а не GetInt32() из-за NULL. Но разница не должна быть заметной в большинстве приложений, но ваше приложение просто делает ничего больше, но читает огромные наборы данных. Большинство приложений не ведут себя так. Сосредоточение на оптимизации самого вызова базы данных (т. Е. Быстрое выполнение запроса) принесет гораздо больший выигрыш в 99,9999% случаев.

1

Я обычно ввести RecordSet класс для этой цели: пример

public class MyObjRecordSet 
{ 
    private readonly IDataReader InnerDataReader; 
    private readonly int OrdinalId; 
    private readonly int OrdinalCreated; 

    public MyObjRecordSet(IDataReader dataReader) 
    { 
     this.InnerDataReader = dataReader; 
     this.OrdinalId = dataReader.GetOrdinal("Id"); 
     this.OrdinalCreated = dataReader.GetOrdinal("Created"); 
    } 

    public int Id 
    { 
     get 
     { 
      return this.InnerDataReader.GetInt32(this.OrdinalId); 
     } 
    } 

    public DateTime Created 
    { 
     get 
     { 
      return this.InnerDataReader.GetDateTime(this.OrdinalCreated); 
     } 
    } 

    public MyObj ToObject() 
    { 
     return new MyObj 
     { 
      Id = this.Id, 
      Created = this.Created 
     }; 
    } 

    public static IEnumerable<MyObj> ReadAll(IDataReader dataReader) 
    { 
     MyObjRecordSet recordSet = new MyObjRecordSet(dataReader); 
     while (dataReader.Read()) 
     { 
      yield return recordSet.ToObject(); 
     } 
    } 
} 

Использования:

List<MyObj> myObjects = MyObjRecordSet.ReadAll(rdr).ToList(); 
+0

Ницца! Считайте это * украденным * ;-) – 5arx

+0

Ха-ха, все в порядке. Я переключился на инфраструктуру сущности, которая автоматически делает этот материал. –

+0

Вы бы порекомендовали EF для небольших веб-приложений? Я посмотрел на него несколько лет назад, и мне показалось, что это тяжело для повседневных приложений, которые я создаю ... – 5arx

1

Для объектов, таких как DateTime я просто отданного РДР значения требуемого класса, но это не могут быть выполнены для таких типов значений, как int

Это не так: DateTime также тип значения и обе следующие работы таким же образом, при условии, что поле ожидаемого типа и не равно нулю:

myObj.Id = (int) rdr["Id"]; 
myObj.Created = (DateTime)rdr["Created"]; 

Если это не работает для вас, может быть, поле, которое вы читаете, является NULL? Или не требуемого типа, и в этом случае вам нужно дважды бросать. Например. для SQL числового поля, вам могут понадобиться:

myObj.Id = (int) (decimal) rdr["Id"]; 
+0

Пробовал это, не работал. Поле, которое я выбрал, является основным ключом в таблице, которую я извлекаю, поэтому я знаю, что не было 'NULL 'значения. Вы можете уточнить? – 5arx

+0

@ 5arx, положить в точку останова отладчика и проверить rdr [" Id "]. GetType(). Если поле SQL является числовым, оно будет десятичным, если это BIGINT, это будет вы должны сначала направить его правильному типу. – Joe

+0

спасибо, я дам ему заглядывать и отчитываться - интригуя. Сказав все это и вернувшись к моему оригинальному вопросу, двойное кастинг кажется большим шагом в неправильном направление - или это? – 5arx