Как эффективно загружать, читать и обрабатывать CSV в C # - PullRequest
0 голосов
/ 17 февраля 2019

Я работаю над сервисом, который будет собирать большой CSV-файл с онлайн-ресурса, затем по мере его загрузки читать строки (желательно в пакетном режиме) и отправлять их в базу данных.При этом не следует использовать более 256 МБ ОЗУ в любое время и не сохранять файл на диск.

Это служба, которая будет запускаться раз в 7 дней и собирать все компании в Норвежском реестре компаний., (изящный, 250 МБ, 1,1 миллиона строк CSV находится здесь: http://hotell.difi.no/download/brreg/enhetsregisteret)

Мое приложение может легко загрузить файл и добавить его в список <>, и обработать его, но этоиспользует 3,3 ГБ ОЗУ

public async Task<bool> CollectAndUpdateNorwegianCompanyRegistry()
{
    var request = await _httpClient.GetAsync(_options.Value.Urls["BrregCsv"]);

    request.EnsureSuccessStatusCode();

    using (var stream = await request.Content.ReadAsStreamAsync())
    using (var streamReader = new StreamReader(stream))
    {
        while (!streamReader.EndOfStream)
        {
            using (var csv = new CsvReader(streamReader)) // CsvReader is from the CsvHelper -nuget
            {
                csv.Configuration.Delimiter = ";";
                csv.Configuration.BadDataFound = null;
                csv.Configuration.RegisterClassMap<NorwegianCompanyClassMap>();

                await _sqlRepository.UpdateNorwegianCompaniesTable(csv.GetRecords<NorwegianCompany>().ToList());
            }
        }
    }

    return true;
}

Небольшое примечание о SqlRepository: я заменил его на простой метод "разрушителя", который просто очищает данные, чтобы не использовать никаких дополнительных ресурсов при отладке

Я ожидал бы, что сборщик мусора «уничтожит» ресурсы, используемые при обработке строк файла, но это не так.

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

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

Спасибо за ваше время и помощь

Ответы [ 2 ]

0 голосов
/ 17 февраля 2019

Еще одно решение, которое я сейчас внедряю, более предсказуемое в использовании ресурсов:

public async Task<bool> CollectAndUpdateNorwegianCompanyRegistryAlternate()
{
    using (var stream = await _httpClient.GetStreamAsync(_options.Value.Urls["BrregCsv"]))
    using (var reader = new StreamReader(stream))
    using (var csv = new CsvReader(reader))
    {
        csv.Configuration.RegisterClassMap<NorwegianCompanyClassMap>();
        csv.Configuration.Delimiter = ";";
        csv.Configuration.BadDataFound = null;

        var tempList = new List<NorwegianCompany>();

        while (csv.Read())
        {
            tempList.Add(csv.GetRecord<NorwegianCompany>());

            if (tempList.Count() > 50000)
            {
                await Task.Factory.StartNew(() => _sqlRepository.UpdateNorwegianCompaniesTable(tempList));

                tempList.Clear();
            }
        }
    }
    return true;
}

Теперь оно использует 3 минуты, но никогда не достигает пика 200 МБ и использует 7-12% ЦП, дажепри выполнении SQL «массовых обновлений» (SqlBulkTool -NuGet отлично подходит для моих нужд), каждые X строк

0 голосов
/ 17 февраля 2019

Таким образом, получение некоторых указателей от Сами Кухмонена ( @ sami-kuhmonen ) помогло, и вот что я придумал:

public async Task<bool> CollectAndUpdateNorwegianCompanyRegistry()
{
    using (var stream = await _httpClient.GetStreamAsync(_options.Value.Urls["BrregCsv"]))
    using (var streamReader = new StreamReader(stream))
    using (var csv = new CsvReader(streamReader))
    {
        csv.Configuration.Delimiter = ";";
        csv.Configuration.BadDataFound = null;
        csv.Configuration.RegisterClassMap<NorwegianCompanyClassMap>();

        await _sqlRepository.UpdateNorwegianCompaniesTable(csv.GetRecords<NorwegianCompany>());
    }

    return true;
}

Он загружает весь файл и отправляет егов SqlRepository за 20 секунд, не превышая 15% ЦП или 30 МБ ОЗУ

Теперь моя следующая задача - SqlRepository, но эта проблема решена

...