Я задаюсь вопросом о состоянии соединения и влиянии на производительность кода с помощью "yield", итерации по объекту считывания данных

Вот мой пример кода, который я использую для извлечения данных из базы данных: на слое DAO:

public IEnumerable<IDataRecord> GetDATA(ICommonSearchCriteriaDto commonSearchCriteriaDto)
{
    using(DbContext)
    {
        DbDataReader reader = DbContext.GetReader("ABC_PACKAGE.GET_DATA", oracleParams.ToArray(), CommandType.StoredProcedure);
        while (reader.Read())
        {
            yield return reader;
        }
    }
}

На слое BO я вызываю вышеупомянутый метод, например:

List<IGridDataDto> GridDataDtos = MapMultiple(_costDriversGraphDao.GetGraphData(commonSearchCriteriaDto)).ToList();

на уровне карты MapMultiple метод определяется следующим образом:

public IGridDataDto MapSingle(IDataRecord dataRecord)
{
    return new GridDataDto
    {
        Code = Convert.ToString(dataRecord["Code"]),
        Name = Convert.ToString(dataRecord["Name"]),
        Type = Convert.ToString(dataRecord["Type"])     
    };
}
public IEnumerable<IGridDataDto> MapMultiple(IEnumerable<IDataRecord> dataRecords)
{
    return dataRecords.Select(MapSingle);
}

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

  • Как долго будет открываться соединение для чтения данных?
  • Когда я рассматриваю только коэффициент производительности кода, полезно ли использовать return yield вместо добавления записи в список и возврата всего списка?

Ответ 1

  • ваш код не показывает, где вы открываете/закрываете соединение; но читатель здесь будет только открыт, пока вы повторяете данные. Отложенное выполнение и т.д. Единственный бит вашего кода, который делает это, - это .ToList(), так что все будет хорошо. В более общем случае да: читатель будет открыт на время, которое вы предпримете для его итерации; если вы выполните .ToList(), который будет минимальным; если вы выполните foreach и (для каждого элемента), выполните внешний HTTP-запрос и подождите 20 секунд, затем да - он будет открыт дольше.
  • Оба имеют свои возможности; небуферизованный подход отлично подходит для огромных результатов, которые вы хотите обрабатывать как поток, без необходимости загружать их в один список в памяти (или даже иметь все из них в памяти за раз); возврат списка быстро закрывает соединение и позволяет избежать случайного использования соединения, хотя он уже имеет открытый считыватель, но не идеален для больших результатов.

Если вы вернете блок итератора, вызывающий может решить, что разумно; если вы всегда возвращаете список, у них не так много вариантов. Третий способ (что мы делаем в dapper) - сделать свой выбор; у нас есть необязательный параметр bool, который по умолчанию "возвращает список", но который вызывающий может изменить, чтобы указать "вернуть блок итератора"; в основном:

bool buffered = true

в параметрах и:

var data = QueryInternal<T>(...blah...);
return buffered ? data.ToList() : data;

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

Ответ 2

Как долго будет открываться соединение для чтения данных?

Соединение будет оставаться открытым до тех пор, пока reader не будет уволено, что означает, что он будет открыт до окончания итерации.

Когда я рассматриваю только коэффициент производительности кода, полезно ли использовать yield return вместо добавления записи в список и возврата всего списка?

Это зависит от нескольких факторов:

  • Если вы не планируете получать весь результат, yield return поможет вам сохранить объем данных, передаваемых в сети.
  • Если вы не планируете преобразовывать возвращенные данные в объекты или несколько строк используются для создания одного объекта, yield return поможет вам сохранить в памяти, используемой в пиковой точке использования вашей программы.
  • Если вы планируете повторять набор результатов enture в течение короткого периода времени, для использования yield return не будет штрафов за производительность. Если итерация длится значительное количество времени на нескольких параллельных потоках, количество открытых курсоров на стороне РСУБД может стать превышено.

Ответ 3

Этот ответ игнорирует недостатки в показанной реализации и охватывает общую идею.

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