EPPlus节省200万行200+列数据表多个excel文件

本文关键字:excel 文件 数据表 节省 200万行 200+ EPPlus | 更新日期: 2023-09-27 18:18:51

我有功能,保存所有记录从SQL表到excel工作表使用EPPlus。如果我导出少量的数据,一切都很好,但有200+列和500000 +行,我得到OutOfMemory异常。

我想修改我的代码在某种程度上能够保存每个文件50000条记录。

下面是我的代码,适用于小数据:

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+列对我来说,5万行是限制。我想知道是否有数量(行x列),这将限制我的导出将正常工作。我希望这个导出函数是通用的,所以当我传递具有50列的datatable时,它将每个文件导出100,000行,对于2列,它将每个文件导出50万行。

EPPlus节省200万行200+列数据表多个excel文件

我在过去使用EPPlus时遇到了内存限制,最终生成了多个.xlsx文件作为解决方案(类似于您的方法)。另一种选择是将编译器设置更改为仅针对64位(如果您可以不支持32位平台)。我记得,EPPlus是针对"任何CPU"编译的,所以如果您可以将代码更改为针对"x64",那么可能会放松内存限制,并允许您生成单个.xlsx文件。在我的情况下,瞄准x64可能会起作用,但我直到事后才想到它,所以我从来没有机会进行测试。

更新:我刚刚使用EPPlus 3.1.3运行了一个快速测试,创建了500,000行,每行70列。在生成内存不足异常之前,我的32位应用程序能够生成大约119,000行。在将目标切换为x64之后,它成功地生成了所有500,000行,尽管这需要很长时间。创建实际的工作表只花了几分钟,但是excelpackage . savea()花了将近20分钟。RAM消耗也相当高(大约11GB RAM)。生成的.xlsx文件大小为220MB, 32位Excel无法打开(内存不足)。底线:瞄准x64可能不是一个可行的解决方案;您最好将输出拆分为多个.xlsx文件。

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

不幸的是,没有简单的方法将这么多数据与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文件中写入的内容。如果你要使用格式和公式,它可能是一个复杂的。

看到:

  • https://www.google.nl/search?q=excel%2520xml%2520format& oq = excel xml % % 2520 2520格式的
  • https://technet.microsoft.com/en-gb/magazine/2006.01.blogtales.aspx
  • http://blogs.msdn.com/b/brian_jones/archive/2005/06/27/433152.aspx

简单的解决方案,没有任何技巧(没有测试,但意图应该是明确的)

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();