Есть ли подводные камни в использовании типа возвращаемого значения IEnumerable <T>для данных SQL? - PullRequest
5 голосов
/ 09 сентября 2009

Мой вопрос касается состояния соединения SQL, загрузки и т. Д. На основе следующего кода:

public IEnumberable<MyType> GetMyTypeObjects()
{
  string cmdTxt = "select * from MyObjectTable";

  using(SqlConnection conn = new SqlConnection(connString))
  {
    using(SqlCommand cmd = new SqlCommand(cmdTxt, conn))
    {
      conn.Open();
      using(SqlDataReader reader = cmd.ExecuteReader())
      {
         while(reader.Read())
         {
            yield return Mapper.MapTo<MyType>(reader);
         }
       }
    }
  }
  yield break;
}

Я вижу, что это может быть проблемой, если существует много процессов, выполняющих похожий код с длительным временем выполнения между итерациями объекта IEnumerable, потому что соединения будут открываться дольше и т. Д. Однако также представляется вероятным, что это уменьшит загрузка ЦП на сервере SQL, поскольку он возвращает данные только при использовании объекта IEnumerable. Это также снижает использование памяти на клиенте, потому что клиент должен загружать только один экземпляр MyType, пока он работает, а не загружать все экземпляры MyType (путем перебора всего DataReader и возврата List или чего-то еще).

  • Есть ли случаи, когда вы можете подумать о том, где бы вы не хотели использовать IEnumerable таким образом, или какие-либо случаи, которые, по вашему мнению, подходят идеально?

  • Какую нагрузку это создает для сервера SQL?

  • Это то, что вы бы использовали в своем собственном коде (исключая упоминания о NHibernate, Subsonic и т. Д.)?

Ответы [ 4 ]

6 голосов
/ 09 сентября 2009

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

2 голосов
/ 09 сентября 2009

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

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

Даже встроенные расширения для перечислителей могут вызывать это, даже если вы используете их правильно:

foreach (MyType item in GetMyTypeObjects().Take(10)) {
   ...
}
2 голосов
/ 09 сентября 2009

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

Я также не ожидаю никакой разницы в нагрузке на SQL Server - запрос уже будет скомпилирован и будет запущен.

1 голос
/ 09 сентября 2009

Мое беспокойство заключается в том, что вы полностью отдаете себя в зависимость от клиентского кода.

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

Пока клиентский код использует foreach, using и т. Д. Или явно вызывает метод Dispose перечислителя, то все в порядке, но ничто не мешает ему сделать что-то вроде этого:

var e = GetMyTypeObjects().GetEnumerator();
e.MoveNext();    // open the connection etc

// forget about the enumerator and go away and do something else
// now the reader, command and connection won't be closed/disposed
// until the GC kicks in and calls their finalisers
...