如何在C#中高效下载、读取和处理CSV
How to efficiently download, read and process CSV in C#
我正在开发一项服务,该服务将从在线资源收集大型 CSV 文件,然后在下载时读取行(最好是分批),并将它们发送到数据库。这应该不会在任何时候使用超过 256MB 的 RAM,并且不会将文件保存到磁盘。
这是一项服务,每 7 天 运行 一次,收集挪威公司登记册中的所有公司,(一个漂亮的 250MB,110 万行 CSV 可以在这里找到:http://hotell.difi.no/download/brreg/enhetsregisteret )
我的应用程序可以轻松下载文件并将其添加到列表<>,然后处理它,但它使用 3.3 GB 的 RAM
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 的小提示:我已将其替换为一个简单的 "destroyer" 方法,该方法仅清除数据,以便在调试时不使用任何额外资源
我希望垃圾收集器 "destroy" 处理文件行时使用的资源,但它没有。
简而言之,我希望发生以下情况:
当 CSV 下载时,它会读取几行,然后将这些发送到一个方法,然后刷新内存中的行
我在处理大型数据集方面绝对没有经验,所以我正在处理其他人的工作,并没有得到我期望的结果
感谢您的宝贵时间和协助
因此,从 Sami Kuhmonen (@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;
}
它会在 20 秒内下载整个文件并将其发送到 SqlRepository,从未超过 15% CPU 或 30MB RAM
现在,我的下一个挑战是 SqlRepository,但这个问题已经解决了
我正在实施的另一个解决方案,它的资源使用更可预测:
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 分钟,但从来没有达到 200MB 的峰值,使用了 7-12% CPU,即使在执行 SQL "bulk updates" 时,(SqlBulkTool -NuGet 非常适合我这里需要),每 X 行
我正在开发一项服务,该服务将从在线资源收集大型 CSV 文件,然后在下载时读取行(最好是分批),并将它们发送到数据库。这应该不会在任何时候使用超过 256MB 的 RAM,并且不会将文件保存到磁盘。
这是一项服务,每 7 天 运行 一次,收集挪威公司登记册中的所有公司,(一个漂亮的 250MB,110 万行 CSV 可以在这里找到:http://hotell.difi.no/download/brreg/enhetsregisteret )
我的应用程序可以轻松下载文件并将其添加到列表<>,然后处理它,但它使用 3.3 GB 的 RAM
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 的小提示:我已将其替换为一个简单的 "destroyer" 方法,该方法仅清除数据,以便在调试时不使用任何额外资源
我希望垃圾收集器 "destroy" 处理文件行时使用的资源,但它没有。
简而言之,我希望发生以下情况: 当 CSV 下载时,它会读取几行,然后将这些发送到一个方法,然后刷新内存中的行
我在处理大型数据集方面绝对没有经验,所以我正在处理其他人的工作,并没有得到我期望的结果
感谢您的宝贵时间和协助
因此,从 Sami Kuhmonen (@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;
}
它会在 20 秒内下载整个文件并将其发送到 SqlRepository,从未超过 15% CPU 或 30MB RAM
现在,我的下一个挑战是 SqlRepository,但这个问题已经解决了
我正在实施的另一个解决方案,它的资源使用更可预测:
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 分钟,但从来没有达到 200MB 的峰值,使用了 7-12% CPU,即使在执行 SQL "bulk updates" 时,(SqlBulkTool -NuGet 非常适合我这里需要),每 X 行