将Excel文档导出到C#中的PostgreSQL数据库中



我正在开发一个功能,负责读取Excel文档,并将其数据导出到PostgreSQL。到目前为止,我有以下过程。在ASP。NET MVC应用程序,我生成了一个页面,请求它输入一个文件。

该文件被读取并保存在流对象中。然后,使用NPOI库,我将此文件转换为实体列表,该列表是根据read中Excel文档中的数据创建的。最后,列表完成后,通过实体框架将其保存到数据库中。这是目前有效的代码。

public async Task<ActionResult> Index([FromForm] IFormFile archivoExcel)
{
try
{
if (archivoExcel.Length > 0)
{
Stream excelStream = archivoExcel.OpenReadStream();
IWorkbook miExcel = null;
if (Path.GetExtension(archivoExcel.FileName) == ".xlsx")
{
miExcel = new XSSFWorkbook(excelStream);
}
else
{
miExcel = new HSSFWorkbook(excelStream);
}
List<ManifiestoExcel> lstManifiestoExcel = new List<ManifiestoExcel>();
var sheet = miExcel.GetSheetAt(0);
for (int i = 1; i < sheet.PhysicalNumberOfRows; i++)
{
var sheetRow = sheet.GetRow(i);
ManifiestoDetalle md = new ManifiestoDetalle();
md.CodigoEntrega        = sheetRow.Cells[0].ToString();
md.Pais                 = sheetRow.Cells[1].ToString();
md.NombreCompleto       = sheetRow.Cells[2].ToString();
md.CodArea              = Convert.ToInt32(sheetRow.Cells[3].ToString());
md.Telefono             = Convert.ToInt32(sheetRow.Cells[4].ToString());
md.Direccion1           = sheetRow.Cells[5].ToString();
md.Direccion2           = sheetRow.Cells[6].ToString();
md.Direccion3           = sheetRow.Cells[7].ToString();
md.Region               = sheetRow.Cells[8].ToString();
md.Comuna               = sheetRow.Cells[9].ToString();
md.CodigoPostal         = Convert.ToInt32(sheetRow.Cells[10].ToString());
md.RutDni               = sheetRow.Cells[11].ToString();
md.DescripcionEnvio     = sheetRow.Cells[12].ToString();
md.Precio               = Convert.ToInt32(sheetRow.Cells[13].ToString());
lstManifiestoExcel.Add(md);
}
await _context.SaveChangesAsync();
}
else
{
ViewBag.Message = "Empty File Upload Failed";
}
}
catch (Exception ex)
{
ViewBag.Message = "File Upload Failed";
}
return View(await _context.Manifiestos.ToListAsync());
}

当记录很少时,此代码可以正常工作。问题产生了,当已经在生产中时,有很多记录,因为它被卡住了。。。

在Python中,我在Heroku的数据库中进行了测试,记录了30000条这种类型的记录,加载不到10秒。我尝试从运行Python脚本。NET核心应用程序,但我没有得到很好的结果,因为使用这种方法你不能使用像NumPy或Pandas这样的库。

有没有一种方法可以从中在PostgreSQL中进行大容量插入。NET核心?我已经查找了一些示例,但它们只适用于SQL Server。

值得一提的是,如果你有很多记录,并且速度是你最关心的问题,我发现没有比使用导出/复制更快的方法来执行这一点,这意味着使用Native Excel的功能(与迭代行相比闪电般快(和Postgres的copy to命令(与逐行插入相比非常快(将文件导出到CSV。我甚至更进一步,在将文件发送到服务器之前对其进行gzip处理,以最大限度地减少网络影响。

这是互操作。。。很少与速度联系在一起,但我告诉你,Excel可以比任何第三方软件包更快地将工作表转换为CSV。

下面的代码是我做的事情的简化版本,你可以声明任何范围吗。这很有帮助,因为如果您的工作表中包含要上传的数据之外的数据,则无法使用本机导出到CSV。代码会将该范围复制到一张空白工作表(稍后将其删除(以启用该功能。

将文件保存到CSV并压缩:

Excel.Range range = excel.Selection;
Excel.Workbook wb = excel.Workbooks.Add();
Excel.Worksheet ws = wb.Worksheets[1];
range.Copy();
ws.get_Range("A1").PasteSpecial(Excel.XlPasteType.xlPasteValuesAndNumberFormats);
excel.DisplayAlerts = false;
wb.SaveAs(Path.Combine(_Outputdir, string.Format("{0}.csv", TableName)),
Excel.XlFileFormat.xlCSV);
wb.Close();
excel.DisplayAlerts = true;
// Pick your favorite compress method -- this is optional
string newFile = Commons.Compress(_Outputdir, string.Format("{0}.csv", TableName));

将压缩的CSV文件发送到Pg服务器并运行副本:

// Send this to the server however you normally would
Commons.FtpPut(newFile, _Outputdir);
NpgsqlTransaction trans = PgConnection.BeginTransaction(IsolationLevel.RepeatableRead);
if (TruncateTable)
{
cmd = new NpgsqlCommand(string.Format("truncate table {0}", TableName),
PgConnection, trans);
cmd.ExecuteNonQuery();
}
try
{
cmd.CommandText = string.Format(
"copy {0} from program 'gzip -dc /apps/external_data/inbound/{0}.csv.gz' " +
"with null as '' csv header encoding 'WIN1250'", TableName);
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (Exception ex)
{
// If the copy fails, roll back the truncate
trans.Rollback();
}
PgConnection.Close();
// Clean up after yourself
Commons.FtpDelete(newFile, _Outputdir);

这是以您有能力访问服务器并运行拷贝为前提的,拷贝是一个超级用户功能。如果你不能做到这些,那么你可以用本地副本(Npgsql上很好地支持(来代替它,但这种方法会大不相同。

这是我的压缩方法,如果你想使用它:

public static string Compress(String Directory, String FileName)
{
string newFileName = string.Format("{0}.gz", FileName);
using (FileStream originalFileStream = File.Open(Path.Combine(Directory, FileName), FileMode.Open))
using (FileStream compressedFileStream = File.Create(Path.Combine(Directory, newFileName)))
using (GZipStream compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
originalFileStream.CopyTo(compressionStream);
return newFileName;
}

最新更新