我面临的挑战是,我有一个由用户创建的SQL,这个SQL现在通过XPO(DevExpress,很快就没有 Package 器,直接通过NPGSQL)运行。在那里,我已经将所有数据加载到内存中,并将结果转换到我自己的 Package 器类中。
现在我使用自己的对象并使用CSVHelper从它创建CSV文件。然后我通过WebAPI将这个文件返回给用户。
由于SQL查询的大小可以是1-2GB,因此内存消耗会大幅增加。
预防这种情况的最好方法是什么?
我以前没有做过太多关于流的工作,目前正在阅读它们。如果我理解正确的话,MemoryStream并没有给我带来太多,因为那里的数据也直接加载到内存中。
[HttpGet("export/data")]
public async Task<IActionResult> ExportData(Guid sqlId)
{
this.OpenConn(); //opens the connection
string sql = this.GetSql(sqlId);
using (NpgsqlCommand command = new NpgsqlCommand(sql, conn))
{
int val;
NpgsqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
// Logic to create the csv file
}
this.CloseConn(); //close the current connection
}
}
我现在可以使用CSVHelper和MemoryStream创建CSV文件,然后返回MemoryStream进行下载。
var ms = new MemoryStream();
var streamWriter = new StreamWriter(ms, Encoding.UTF8);
var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
// Logic to create the csv file, with the reader from npgsql
// [...]
return File(ms, "text/csv", "export.csv");
如果我理解正确的话,这并没有给我带来很多好处,因为我已经将数据存储在MemoryStream中,因此内存无论如何都会被填满。处理这个问题的最好办法是什么?将CSV文件写入磁盘上的临时目录,然后返回它?例如,我可以在这里访问S3 Bucket,或者在Kubernetes中使用我自己的目录。如果我通过文件返回流,我不能在成功传输后简单地删除文件,可以吗?我不能做最后的决定吗?实际上,我只想为请求传输文件,而不是将其存储在某个地方。
谢谢你,谢谢
2条答案
按热度按时间svgewumm1#
最简单的方法是将文件保存在本地并进行流式传输。
或者,你可以尝试直接写入
Response.Body
(不要忘记刷新)或使用Stephen Cleary的FileCallbackResult
方法:和样品用法:
但总的来说,如果可能的话,我建议重新考虑这种方法。如果这是某个ETL管道的导出-例如,您可以切换到仅将文件上传到某个共享位置。
gpnt7bae2#
我维护了一个nuget包,可以让这很容易:Sylvan.AspNetCore.Mvc.Formatters.Csv。
它使用Sylvan.Data.Csv库实现了CSV的MVC格式化程序。配置MVC服务时,在启动时注册格式化程序:
在控制器操作中,您可以直接返回
DbDataReader
,并使用Produces
属性指定“text/csv”作为内容类型:该实现在CPU和内存使用方面都非常高效,并且完全异步。响应将被流式传输到客户端,并且不会在内存中完全缓冲。如果使用EFCore或其他ORM,也可以返回
IEnumerable<T>
或IAsyncEnumerable<T>
,而不是DbDataReader
。Sylvan.AspNetCore.Mvc.Formatters.Excel
包提供了一个类似的格式化程序,可以返回Excel(.xlsx)文件。这需要添加包并注册格式化程序opts.AddSylvanExcelFormatters()
。然后,您可以添加第二个端点,以允许将结果作为Excel文件获取:
.xlsx响应必须被缓冲,并且由于文件格式的结构而不能完全流式传输。但是,响应有效负载可能比CSV小得多,因为xlsx文件格式是压缩的,并且重用共享字符串。