EPPlus将200万行数据表存储到多个excel文件中

时间:2015-10-21 16:01:44

标签: c# .net-4.5 epplus epplus-4

我有使用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 个答案:

答案 0 :(得分:8)

我在过去使用EPPlus时遇到了内存限制,并最终生成了多个.xlsx文件作为解决方法(类似于您的方法)。另一种方法是将编译器设置更改为仅针对64位(如果您可以在不支持32位平台的情况下使用)。我记得,EPPlus是针对&#34;任何CPU&#34;编译的,所以如果你可以将代码更改为目标&#34; x64&#34;这可能会放松内存限制,并允许您生成单个.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文件。

我很想删除这个答案,因为它已经证明是一个死路一条,但我决定留下它以防万一它可以帮助别人在将来避开这条道路。

答案 1 :(得分:4)

不幸的是,没有简单的方法可以将大量数据与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);
}

答案 2 :(得分:0)

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

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

当然,你必须手动弄清楚你需要在.XML文件中写什么。你要使用格式和公式,它可能是一个复杂的。

见这里:

答案 3 :(得分: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();