【问题标题】:c# working on excel files with large datac# 处理大数据的excel文件
【发布时间】:2018-09-06 22:38:15
【问题描述】:

我正在将不同 Excel 文件的第一张表中的数据复制到单个工作簿中。我已经尝试过使用不同的替代方案,如npoispire.xlsInterop,效果很好,但它浪费了我太多的时间。如果有人能给我推荐一个更好的,那将非常感激。在网上翻了很多表格,都找不到。

仅供参考:我的每个文件的大小都超过 50 MB。有些是 10 MB 或更少。

这是我尝试过的一种(使用 Spire.xls):

workbook = new Workbook();
//laod first file
workbook.LoadFromFile(names[0]);

//load the remaining files starting with second file
for (int i = 1; i < cnt; i++)
{
    LoadFIle(names[i]);
    //merge the loaded file immediately and than load next file
    MergeData();
}

private void LoadFIle(string filePath)
{
     //load other workbooks starting with 2nd workbbook
     tempbook = new Workbook();
     tempbook.LoadFromFile(filePath);
}

private void MergeData()
{
    try
    {
        int c1 = workbook.ActiveSheet.LastRow, c2 = tempbook.Worksheets[0].LastRow;

        //check if you have exceeded 1st sheet limit
        if ((c1 + c2) <= 1048575)
        {
           //import the second workbook's worksheet into the first workbook using a datatable
           //load 1st sheet of tempbook into sheet
           Worksheet sheet = tempbook.Worksheets[0];
           //copy data from sheet into a datatable
           DataTable dataTable = sheet.ExportDataTable();
           //load sheet1
           Worksheet sheet1 = workbook.Worksheets[workbook.ActiveSheetIndex];
           sheet1.InsertDataTable(dataTable, false, sheet1.LastRow + 1, 1);
       }
       else if ((c1 >= 1048575 && c2 >= 1048575) || c1 >= 1048575 || c2 >= 1048575 || (c1 + c2) >= 1048575)
       {
           workbook.Worksheets.AddCopy(tempbook.Worksheets[0]);
           indx = workbook.ActiveSheet.Index;
           workbook.ActiveSheetIndex = ++indx;
       }
       else
       {
           //import the second workbook's worksheet into the first workbook using a datatable
          //load 1st sheet of tempbook into sheet
           Worksheet sheet = tempbook.Worksheets[0];
           //copy data from sheet into a datatable
           DataTable dataTable = sheet.ExportDataTable();
           //load sheet1
           Worksheet sheet1 = workbook.Worksheets[workbook.ActiveSheetIndex];
           sheet1.InsertDataTable(dataTable, false, sheet1.LastRow + 1, 1);
      }
   }
   catch (IndexOutOfRangeException)
   {

   }
}
}

嗯,这很好用,但如上所述需要很长时间。欢迎任何建议。提前致谢。

【问题讨论】:

  • 我知道这不是您想要的答案,但您为什么要在电子表格中处理大型数据集?数据库擅长这项任务。如果您需要将数据提取到工作表中,Excel 可以查询数据库。
  • @Neil:这是客户要求。帮不上忙。这些文件也来自客户端。
  • 客户端可能是错误的。你需要反击并说“要么需要很长时间,要么你应该这样做'这样'会更快”。
  • 50MB 不是很多数据。 Excel 可以使用 PowerPivot 处理 许多 百万行数据。它与 SSAS 使用的压缩内存列存储引擎相同。 Excel 的 PowerQuery 几乎是一个完整的 ETL 工具,可以轻松合并来自多个源的数据,包括数据库、Hadoop 等。合并 Excel 源非常容易
  • 我猜您使用的是 .xls 文件而不是 .xlsx? Excel 互操作也可以很快,当您只想复制内容时更是如此。就像选择范围一样,将其复制到数组中,在新工作表中选择范围并将其粘贴到那里。这是非常基本的东西。就像你说的:“有一个解决方案,但是需要1天的时间来学习,所以我不尝试,给我一个更好的解决方案”

标签: c# excel winforms


【解决方案1】:

这是我使用 Excel 互操作的(我知道的最快的)实现。虽然我仔细看了看全部释放(肯定漏掉了一个),进程列表中还有2个Excel实例,程序结束后它们都被关闭了。

关键是只有 2 个 Open Excel 实例并使用 Range.Value2 将数据复制为块。

//Helper function to cleanup
public void ReleaseObject(object obj)
{
    if (obj != null && Marshal.IsComObject(obj))
    {
        Marshal.ReleaseComObject(obj);
    }
}


public void CopyIntoOne(List<string> pSourceFiles, string pDestinationFile)
{

    var sourceExcelApp = new Microsoft.Office.Interop.Excel.Application();
    var destinationExcelApp = new Microsoft.Office.Interop.Excel.Application();

    // TODO: Check if it exists
    destinationExcelApp.Workbooks.Open(pDestinationFile);
    // for debug
    //destinationExcelApp.Visible = true;
    //sourceExcelApp.Visible = true;
    int i = 0;
    var sheets = destinationExcelApp.ActiveWorkbook.Sheets;
    var lastsheet = destinationExcelApp.ActiveWorkbook.Sheets[sheets.Count];
    ReleaseObject(sheets);
    foreach (var srcFile in pSourceFiles)
    {
        sourceExcelApp.Workbooks.Open(srcFile);
        // get extends
        var lastRow = sourceExcelApp.ActiveSheet.Cells.Find("*", System.Reflection.Missing.Value,
            System.Reflection.Missing.Value, System.Reflection.Missing.Value, XlSearchOrder.xlByRows,
            XlSearchDirection.xlPrevious, false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
        var lastCol = sourceExcelApp.ActiveSheet.Cells.Find("*", System.Reflection.Missing.Value, System.Reflection.Missing.Value,
            System.Reflection.Missing.Value, XlSearchOrder.xlByColumns, XlSearchDirection.xlPrevious, false,
            System.Reflection.Missing.Value, System.Reflection.Missing.Value);
        var startCell = (Range) sourceExcelApp.ActiveWorkbook.ActiveSheet.Cells[1, 1];
        var endCell = (Range) sourceExcelApp.ActiveWorkbook.ActiveSheet.Cells[lastRow.Row, lastCol.Column];
        var myRange = sourceExcelApp.ActiveWorkbook.ActiveSheet.Range[startCell, endCell];
        // copy the values
        var value = myRange.Value2;

        // create sheet in new Workbook at the end                
        Worksheet newSheet = destinationExcelApp.ActiveWorkbook.Sheets.Add(After: lastsheet);
        ReleaseObject(lastsheet);
        lastsheet = newSheet;
        //its even faster when adding it at the front
        //Worksheet newSheet = destinationExcelApp.ActiveWorkbook.Sheets.Add();

        // change that to a good name
        newSheet.Name = ++i + "";

        var dstStartCell = (Range) destinationExcelApp.ActiveWorkbook.ActiveSheet.Cells[1, 1];
        var dstEndCell = (Range) destinationExcelApp.ActiveWorkbook.ActiveSheet.Cells[lastRow.Row, lastCol.Column];
        var dstRange = destinationExcelApp.ActiveWorkbook.ActiveSheet.Range[dstStartCell, dstEndCell];
        // this is the actual paste
        dstRange.Value2 = value;
        //cleanup

        ReleaseObject(startCell);
        ReleaseObject(endCell);
        ReleaseObject(myRange);
        ReleaseObject(value);// cannot hurt, but not necessary since its a simple array
        ReleaseObject(dstStartCell);
        ReleaseObject(dstEndCell);
        ReleaseObject(dstRange);
        ReleaseObject(newSheet);
        ReleaseObject(lastRow);
        ReleaseObject(lastCol);
        sourceExcelApp.ActiveWorkbook.Close(false);

    }
    ReleaseObject(lastsheet);

    sourceExcelApp.Quit();
    ReleaseObject(sourceExcelApp);
    destinationExcelApp.ActiveWorkbook.Save();
    destinationExcelApp.Quit();
    ReleaseObject(destinationExcelApp);

    destinationExcelApp = null;
    sourceExcelApp = null;

}

我已经在小型 excel 文件上对其进行了测试,并且很好奇它在处理大型文件时的表现。

【讨论】:

  • 感谢您的解决方法朋友!但这不起作用。它将源文件的每张表复制到目标文件的不同表中,而不是将其附加到一张表中。将工作表复制到另一个工作簿非常简​​单,而且速度明显很快。但是我通过保留其格式和每一个细节来复制目标工作表中的每个工作表数据。
  • 嗯,应该很容易调整它来做到这一点。只需将目标范围的起始dstStartCelldstEndCell“移动”到当前位置,并删除工作表的“添加”逻辑。这将更快,因为没有附加新工作表(每个输入文件少 1 个 COM+ 操作)。您的大数据花了多长时间?
  • 那么复制格式和每个细节(无论这意味着什么)不是您问题的一部分,但复制格式不超过几个命令,请参阅此处了解详细信息:stackoverflow.com/a/18543010/1037841 有还有一个方法Range.CopyRange.Paste 不会慢很多。对于一般信息,想象一下用户会做什么来获得结果,然后重新建模步骤。可能是:全选,单击复制,滚动到最后一行,单击粘贴。 Interop 会在用户体验时立即执行此操作。
  • 正如您所说,该解决方案非常适用于小文件。但是对于大于 5 MB 的文件,它会被挂起。
  • 好的,谢谢您的回答。请在找到解决方案时发布您的解决方案,因为这是一件有趣的事情。
猜你喜欢
  • 1970-01-01
  • 2013-12-14
  • 1970-01-01
  • 2023-03-13
  • 2015-10-30
  • 1970-01-01
  • 2012-05-10
  • 2021-01-12
  • 1970-01-01
相关资源
最近更新 更多