2

我有使用 EPPlus 将 SQL 表中的所有记录保存到 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语句中的所有内容,而在第二个版本中,我调用了两次相同的代码。

  1. 如何修复我的代码以删除重复代码并将所有内容放入其中。
  2. 我可以将下一组(50 000 条记录)添加为新工作表而不是创建新文件吗?
  3. 将数据保存到文件时 EPPlus 限制是多少?rows x columns? 我发现 EPPlus 应该处理超过百万行的信息,但不像我拥有的​​那么多列。我认为我可以用单列导出数百万行,但对于 200 多列对我来说,50 000 行是有限的。我想知道是否有数量(行 x 列)会限制我的导出工作正常。我希望该导出功能是通用的,因此当我传递具有 50 列的数据表时,它将导出例如每个文件 100 000 行,而对于 2 列,它将导出每个文件 50 万行。
4

4 回答 4

9

我过去曾遇到过 EPPlus 的内存限制,最终生成了多个 .xlsx 文件作为解决方法(类似于您的方法)。另一种选择是将编译器设置更改为仅针对 64 位(如果您可以在不支持 32 位平台的情况下通过)。我记得,EPPlus 是为“任何 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 文件。

我很想删除这个答案,因为它已经证明是一个死胡同,但决定留下它,以防它帮助其他人在未来避免这条路。

于 2015-12-02T05:56:51.247 回答
3

不幸的是,没有简单的方法可以将这么多数据与 Epplus 合并到一个文件中。基本上,整个文件在打开时都会被加载到内存中——要么全部加载,要么全部加载。理论上,您可以生成 XLSX 包含的 XML 文件(它们是重命名的 zip 文件)并手动插入它,因为它的内存占用会更小,但这可不是一件小事。

对于您当前的代码,.dispose()如果您想避免使用 using 语句,您总是可以手动调用。但我了解您希望避免重复代码。像这样的事情怎么样(但在复制所有对象数据时注意内存使用情况):

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);
}
于 2015-10-21T23:25:31.720 回答
0

由于您正在创建一个新的 excel 文件(如果我错了,请纠正我),您可以简单地编写一个包含一些特定内容的 XML 文件。Excel 支持 .xml 文件,如果它们包含正确的内容。

您可以简单地在内存中创建 XML 文件的内容,然后将此内容写入 .XML 文件。您不需要 EPPlus 包,因此您绕过了 EPPlus 包的限制。

当然,您必须手动确定需要在 .XML 文件中写入的内容。你们将使用格式和公式,它可能是一个复杂的。

看这里:

于 2015-11-26T10:50:13.437 回答
0

没有任何技巧的简单解决方案(未经测试,但意图应该很清楚)

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();
于 2015-11-26T11:15:45.100 回答