Повышение производительности SqlBulkCopy с помощью IDataReader для IEnumerable <object []> - PullRequest
0 голосов
/ 31 мая 2019

Я выполняю рефакторинг некоторого кода, который извлекает данные из динамических электронных таблиц Excel или из файла CSV в исходном формате IEnumerable, а затем сохраняет его с помощью SqlBulkCopy из ADO.NET.В настоящее время для сохранения 500 000 строк данных требуется около 43 секунд.Исходный код преобразовал IEnumerable в DataTable перед передачей его в SqlBulkCopy.WriteToServer (dt).Этот процесс преобразования, а также сохранение данных также заняли довольно много времени.Таблица назначения для массового копирования в только что созданную перед массовым копированием без данных и без индексов (но с двумя ограничениями по умолчанию. Я попытался отключить их).После прочтения различных статей (включая использование FastMember) об улучшении скорости с использованием Forward Only DataReader (IDataReader), чтобы предотвратить загрузку всего объекта в память, я подумал, что реализую свою собственную для IEnumerable.Мне удалось сократить время подготовки MyDataReader> до чуть более секунды, но затем я заметил, что SQLBulkCopy собирает бананы.Все время, которое я сохранил, было смещено, но на этот раз внутри функции SqlBulkCopy.WriteToServer (dt).Я вижу, как он вызывается 11 раз, что соответствует 11 партиям по 50000 строк.Похоже, это трогательно пытается прочитать и записать каждое отдельное значение строки и столбца, запустив его 4,6 миллиона раз.Я думал, что IDataReader должен был быть быстрым?Как мне сделать это быстрее?Код и dotTrace ниже:

Пример исходных данных

EnumerableDataReader<object[]>
EnumerableDataReader[0] = object[]{"Column1", "Column2", "Column3", "Column4", "Column5"}
EnumerableDataReader[1] = object[]{"Data1", "Data2", "Data3", "Data4"}
EnumerableDataReader[2] = object[]{"Data1", "Data2", "Data3", "Data4"}

Реализация IDataReader

public class EnumerableDataReader<T> : IDataReader
    where T : IEnumerable
{
    private IEnumerator<T> _iterator;

    public EnumerableDataReader(IEnumerable<T> list)
    {
        this._iterator = list.GetEnumerator();
    }

    public void Close()
    {
        _iterator.Dispose();
    }

    public bool Read()
    {
        return _iterator.MoveNext();
    }

    public void Dispose()
    {
        Close();
    }


    public Type GetFieldType(int i)
    {
        return ((IEnumerable<object>)_iterator.Current)?.ElementAt(i).GetType();
    }

    public object GetValue(int i)
    {
        return ((IEnumerable<object>) _iterator.Current)?.ElementAt(i);
    }

    public int GetValues(object[] values)
    {
        values = (object[]) (IEnumerable<object>)_iterator.Current;

        return values.Length;
    }

    public int FieldCount
    {
        get
        {
            return _iterator.Current != null ? ((IEnumerable<object>) _iterator.Current).Count() : 0;
        }
    }

Сохранениекод:

    public void bulk_copy_data(string destinationTable, IDataReader dt, int fieldCount, FieldMappingInfo fi)
    {
        try
        {
        using (SqlBulkCopy bulk = new SqlBulkCopy(Connection(), SqlBulkCopyOptions.TableLock, null))
        {
            bulk.DestinationTableName = destinationTable;
            bulk.BulkCopyTimeout = 500;
            bulk.BatchSize = 50000;


            for (int i = 0; i < fieldCount - 1; i++)
            {
                bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping() { SourceOrdinal = i, DestinationOrdinal = i });
            }

            bulk.WriteToServer(dt);
        }

Выход dotTrace dotTrace output

...