EPPlus 将具有 200 多列数据表的 200 万行保存到多个 excel 文件
EPPlus save two million rows with 200+ columns datatable to multiple excel files
我有使用 EPPlus 将所有记录从 SQL table 保存到 excel 工作表的功能。
如果我导出少量数据,一切正常,但如果有 200 多列和 500 000 多行,我会遇到 OutOfMemory 异常。
我想修改我的代码,使每个文件能够保存 50 000 条记录。
这是我的适用于小数据的代码:
private Task SaveAsync(string tableName)
{
return Task.Run(() =>
{
try
{
using (var conn = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
var fileName = string.Format(TargetFile, tableName);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
sdr.Read();
var numberOfRecordsInTable = sdr.GetInt32(0);
sdr.NextResult();
using (ExcelPackage pck = new ExcelPackage(new FileInfo(fileName)))
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Results");
int count = sdr.FieldCount;
int col = 1, row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
while (sdr.Read())
{
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
}
//autosize
ws.Cells[ws.Dimension.Address].AutoFitColumns();
//autofiltr
ws.Cells[1, 1, 1, count].AutoFilter = true;
}
}
conn.Close();
}
}
}
catch (Exception e)
{
Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(e);
}
});
}
和我修改后的代码,每个文件拆分记录 50 000:
private Task SaveAsync2(string tableName)
{
return Task.Run(() =>
{
try
{
using (var conn = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
var fileName = string.Format(TargetFile, tableName,"");
if (File.Exists(fileName))
{
File.Delete(fileName);
}
sdr.Read();
var max = sdr.GetInt32(0);
int filesCount = 1;
if (max > 50000)
{
fileName = string.Format(TargetFile, tableName, filesCount);
}
sdr.NextResult();
ExcelPackage pck = new ExcelPackage(new FileInfo(fileName));
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("RESULTS");
int count = sdr.FieldCount;
int col = 1, row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
while (sdr.Read())
{
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
if (row > 50000)
{
pck.Save();
filesCount++;
fileName = string.Format(TargetFile, tableName, filesCount);
pck = new ExcelPackage(new FileInfo(fileName));
ws = pck.Workbook.Worksheets.Add("RESULTS");
count = sdr.FieldCount;
col = 1;
row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
}
}
//autosize
ws.Cells[ws.Dimension.Address].AutoFitColumns();
//autofiltr
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
}
}
conn.Close();
}
}
catch (Exception e)
{
Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(e);
}
});
}
基本上这工作正常,但在我的代码的第一个版本中,我使用了 using
语句中的所有内容,而在第二个版本中,我调用了相同的代码两次。
- 如何修复我的代码以删除重复代码并将所有内容放入 using 中。
- 我可以添加下一组(50 000 条记录)作为新工作表而不是创建新文件吗?
- 将数据保存到文件时,EPPlus 限制是多少?
rows x columns
?我发现 EPPlus 应该处理超过百万行的信息,但没有我拥有的那么多列。我认为我可以用单列导出百万行,但对于 200 多列来说,50 000 行是限制。我想知道是否有数字(行 x 列)会限制我的导出工作正常。我希望该导出功能是通用的,所以当我传递 datatable 50 列时,它将导出例如每个文件 100 000 行,对于 2 列,它将每个文件导出 50 万行。
不幸的是,没有简单的方法可以将这么多数据与 Epplus 合并到一个文件中。基本上,整个文件在打开时被加载到内存中——要么全有,要么全无。理论上,您可以生成 XLSX 包含的 XML 文件(它们是重命名的 zip 文件)并手动插入它,因为它占用的内存较小,但这不是一件容易的事。
对于您当前的代码,如果您想避免使用 using 语句,您总是可以手动调用 .dispose()
。但我知道您想避免重复代码。像这样的事情怎么样(但在复制所有对象数据时注意内存使用情况):
const int max = 10;
var loop = 0;
using (var sdr = cmd.ExecuteReader())
{
var fieldcount = sdr.FieldCount;
var getfi = new Func<int, FileInfo>(i =>
{
var fi = new FileInfo(String.Format(@"c:\temp\Multi_Files{0}.xlsx", i));
if (fi.Exists) fi.Delete();
return fi;
});
var savefile = new Action<FileInfo, List<Object[]>>((info, rows) =>
{
using (var pck = new ExcelPackage(info))
{
var wb = pck.Workbook;
var ws = wb.Worksheets.Add("RESULTS");
for (var row = 0; row < rows.Count; row++)
for (var col = 0; col < fieldcount; col++)
ws.SetValue(row + 1, col + 1, rows[row][col]);
pck.Save();
}
});
var rowlist = new List<Object[]>();
while (sdr.Read())
{
var rowdata = new Object[sdr.FieldCount];
sdr.GetValues(rowdata);
rowlist.Add(rowdata);
if (rowlist.Count == max)
{
savefile(getfi(++loop), rowlist);
rowlist.Clear();
}
}
if (rowlist.Count > 0)
savefile(getfi(++loop), rowlist);
}
由于您正在创建一个新的 excel 文件(如果我错了请纠正我),您可以简单地编写一个包含一些特定内容的 XML 文件。 Excel 支持 .xml 文件,如果它们包含正确的内容。
您可以简单地在内存中创建 XML 文件的内容,然后将此内容写入 .XML 文件。您不需要 EPPlus 包,因此您绕过了 EPPlus 包的限制。
当然,你必须手动弄清楚你需要在.XML文件中写入什么。你们中的一些人将要使用格式和公式,这可能很复杂。
看这里:
没有任何技巧的简单解决方案(未测试,但意图应该很清楚)
using (var conn = new SqlConnection(_connectionString))
{
int filesCount = 1;
int col = 1, row = 1;
string fileName = String.Empty;
int count;
ExcelPackage pck;
ExcelWorksheet ws;
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
while (sdr.Read())
{
if (row == 1)
{
fileName = string.Format(TargetFile, tableName, filesCount);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
pck = new ExcelPackage(new FileInfo(fileName));
ws = pck.Workbook.Worksheets.Add("RESULTS");
}
count = sdr.FieldCount;
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
if (row >= 50000)
{
ws.Cells[ws.Dimension.Address].AutoFitColumns();
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
row = 1;
filesCount+
}
}
}
if (row > 1)
{
ws.Cells[ws.Dimension.Address].AutoFitColumns();
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
}
}
}
conn.Close();
我过去曾 运行 遇到过 EPPlus 的内存限制,最终生成多个 .xlsx 文件作为解决方法(类似于您的方法)。另一种选择是将您的编译器设置更改为仅针对 64 位(如果您可以在不支持 32 位平台的情况下通过)。我记得,EPPlus 是为 "Any CPU" 编译的,因此如果您可以将代码更改为目标 "x64",这可能会放宽内存限制并允许您生成单个 .xlsx 文件。以 x64 为目标在我的情况下可能会奏效,但我直到事后才想到它,所以我一直没有机会进行测试。
更新:
我只是 运行 使用 EPPlus 3.1.3 创建 500,000 行,每行 70 列的快速测试。在生成内存不足异常之前,我的 32 位应用程序能够生成大约 119,000 行。将目标切换到 x64 后,它成功生成了所有 500,000 行,尽管它花了很长时间。创建实际的工作表只花了几分钟,但 ExcelPackage.SaveAs() 花了将近 20 分钟。 RAM 消耗也相当高(大约 11GB 的 RAM)。生成的 .xlsx 为 220MB,32 位 Excel 无法打开(内存不足)。 底线:针对 x64 可能不是一个可行的解决方案;您最好将输出拆分为多个 .xlsx 文件。
我很想删除这个答案,因为它已被证明是一个死胡同,但决定保留它以防它帮助其他人在未来避免这条路。
我有使用 EPPlus 将所有记录从 SQL table 保存到 excel 工作表的功能。 如果我导出少量数据,一切正常,但如果有 200 多列和 500 000 多行,我会遇到 OutOfMemory 异常。
我想修改我的代码,使每个文件能够保存 50 000 条记录。
这是我的适用于小数据的代码:
private Task SaveAsync(string tableName)
{
return Task.Run(() =>
{
try
{
using (var conn = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
var fileName = string.Format(TargetFile, tableName);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
sdr.Read();
var numberOfRecordsInTable = sdr.GetInt32(0);
sdr.NextResult();
using (ExcelPackage pck = new ExcelPackage(new FileInfo(fileName)))
{
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Results");
int count = sdr.FieldCount;
int col = 1, row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
while (sdr.Read())
{
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
}
//autosize
ws.Cells[ws.Dimension.Address].AutoFitColumns();
//autofiltr
ws.Cells[1, 1, 1, count].AutoFilter = true;
}
}
conn.Close();
}
}
}
catch (Exception e)
{
Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(e);
}
});
}
和我修改后的代码,每个文件拆分记录 50 000:
private Task SaveAsync2(string tableName)
{
return Task.Run(() =>
{
try
{
using (var conn = new SqlConnection(_connectionString))
{
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
var fileName = string.Format(TargetFile, tableName,"");
if (File.Exists(fileName))
{
File.Delete(fileName);
}
sdr.Read();
var max = sdr.GetInt32(0);
int filesCount = 1;
if (max > 50000)
{
fileName = string.Format(TargetFile, tableName, filesCount);
}
sdr.NextResult();
ExcelPackage pck = new ExcelPackage(new FileInfo(fileName));
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("RESULTS");
int count = sdr.FieldCount;
int col = 1, row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
while (sdr.Read())
{
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
if (row > 50000)
{
pck.Save();
filesCount++;
fileName = string.Format(TargetFile, tableName, filesCount);
pck = new ExcelPackage(new FileInfo(fileName));
ws = pck.Workbook.Worksheets.Add("RESULTS");
count = sdr.FieldCount;
col = 1;
row = 1;
for (int i = 0; i < count; i++)
{
ws.SetValue(row, col++, sdr.GetName(i));
}
row++;
col = 1;
}
}
//autosize
ws.Cells[ws.Dimension.Address].AutoFitColumns();
//autofiltr
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
}
}
conn.Close();
}
}
catch (Exception e)
{
Debug.WriteLine("Error at: " + Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(e);
}
});
}
基本上这工作正常,但在我的代码的第一个版本中,我使用了 using
语句中的所有内容,而在第二个版本中,我调用了相同的代码两次。
- 如何修复我的代码以删除重复代码并将所有内容放入 using 中。
- 我可以添加下一组(50 000 条记录)作为新工作表而不是创建新文件吗?
- 将数据保存到文件时,EPPlus 限制是多少?
rows x columns
?我发现 EPPlus 应该处理超过百万行的信息,但没有我拥有的那么多列。我认为我可以用单列导出百万行,但对于 200 多列来说,50 000 行是限制。我想知道是否有数字(行 x 列)会限制我的导出工作正常。我希望该导出功能是通用的,所以当我传递 datatable 50 列时,它将导出例如每个文件 100 000 行,对于 2 列,它将每个文件导出 50 万行。
不幸的是,没有简单的方法可以将这么多数据与 Epplus 合并到一个文件中。基本上,整个文件在打开时被加载到内存中——要么全有,要么全无。理论上,您可以生成 XLSX 包含的 XML 文件(它们是重命名的 zip 文件)并手动插入它,因为它占用的内存较小,但这不是一件容易的事。
对于您当前的代码,如果您想避免使用 using 语句,您总是可以手动调用 .dispose()
。但我知道您想避免重复代码。像这样的事情怎么样(但在复制所有对象数据时注意内存使用情况):
const int max = 10;
var loop = 0;
using (var sdr = cmd.ExecuteReader())
{
var fieldcount = sdr.FieldCount;
var getfi = new Func<int, FileInfo>(i =>
{
var fi = new FileInfo(String.Format(@"c:\temp\Multi_Files{0}.xlsx", i));
if (fi.Exists) fi.Delete();
return fi;
});
var savefile = new Action<FileInfo, List<Object[]>>((info, rows) =>
{
using (var pck = new ExcelPackage(info))
{
var wb = pck.Workbook;
var ws = wb.Worksheets.Add("RESULTS");
for (var row = 0; row < rows.Count; row++)
for (var col = 0; col < fieldcount; col++)
ws.SetValue(row + 1, col + 1, rows[row][col]);
pck.Save();
}
});
var rowlist = new List<Object[]>();
while (sdr.Read())
{
var rowdata = new Object[sdr.FieldCount];
sdr.GetValues(rowdata);
rowlist.Add(rowdata);
if (rowlist.Count == max)
{
savefile(getfi(++loop), rowlist);
rowlist.Clear();
}
}
if (rowlist.Count > 0)
savefile(getfi(++loop), rowlist);
}
由于您正在创建一个新的 excel 文件(如果我错了请纠正我),您可以简单地编写一个包含一些特定内容的 XML 文件。 Excel 支持 .xml 文件,如果它们包含正确的内容。
您可以简单地在内存中创建 XML 文件的内容,然后将此内容写入 .XML 文件。您不需要 EPPlus 包,因此您绕过了 EPPlus 包的限制。
当然,你必须手动弄清楚你需要在.XML文件中写入什么。你们中的一些人将要使用格式和公式,这可能很复杂。
看这里:
没有任何技巧的简单解决方案(未测试,但意图应该很清楚)
using (var conn = new SqlConnection(_connectionString))
{
int filesCount = 1;
int col = 1, row = 1;
string fileName = String.Empty;
int count;
ExcelPackage pck;
ExcelWorksheet ws;
using (var cmd = new SqlCommand(string.Format(DataQuery, tableName), conn))
{
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 360;
conn.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
while (sdr.Read())
{
if (row == 1)
{
fileName = string.Format(TargetFile, tableName, filesCount);
if (File.Exists(fileName))
{
File.Delete(fileName);
}
pck = new ExcelPackage(new FileInfo(fileName));
ws = pck.Workbook.Worksheets.Add("RESULTS");
}
count = sdr.FieldCount;
for (int i = 0; i < count; i++)
{
var val = sdr.GetValue(i);
ws.SetValue(row, col++, val);
}
row++;
col = 1;
if (row >= 50000)
{
ws.Cells[ws.Dimension.Address].AutoFitColumns();
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
row = 1;
filesCount+
}
}
}
if (row > 1)
{
ws.Cells[ws.Dimension.Address].AutoFitColumns();
ws.Cells[1, 1, 1, count].AutoFilter = true;
pck.Save();
}
}
}
conn.Close();
我过去曾 运行 遇到过 EPPlus 的内存限制,最终生成多个 .xlsx 文件作为解决方法(类似于您的方法)。另一种选择是将您的编译器设置更改为仅针对 64 位(如果您可以在不支持 32 位平台的情况下通过)。我记得,EPPlus 是为 "Any CPU" 编译的,因此如果您可以将代码更改为目标 "x64",这可能会放宽内存限制并允许您生成单个 .xlsx 文件。以 x64 为目标在我的情况下可能会奏效,但我直到事后才想到它,所以我一直没有机会进行测试。
更新: 我只是 运行 使用 EPPlus 3.1.3 创建 500,000 行,每行 70 列的快速测试。在生成内存不足异常之前,我的 32 位应用程序能够生成大约 119,000 行。将目标切换到 x64 后,它成功生成了所有 500,000 行,尽管它花了很长时间。创建实际的工作表只花了几分钟,但 ExcelPackage.SaveAs() 花了将近 20 分钟。 RAM 消耗也相当高(大约 11GB 的 RAM)。生成的 .xlsx 为 220MB,32 位 Excel 无法打开(内存不足)。 底线:针对 x64 可能不是一个可行的解决方案;您最好将输出拆分为多个 .xlsx 文件。
我很想删除这个答案,因为它已被证明是一个死胡同,但决定保留它以防它帮助其他人在未来避免这条路。