【问题标题】:How to speed up dumping a DataTable into an Excel worksheet?如何加快将 DataTable 转储到 Excel 工作表中的速度?
【发布时间】:2011-02-11 04:30:01
【问题描述】:

我有以下例程将 DataTable 转储到 Excel 工作表中。

    private void RenderDataTableOnXlSheet(DataTable dt, Excel.Worksheet xlWk, 
                                    string [] columnNames, string [] fieldNames)
    {
        // render the column names (e.g. headers)
        for (int i = 0; i < columnNames.Length; i++)
            xlWk.Cells[1, i + 1] = columnNames[i];

        // render the data 
        for (int i = 0; i < fieldNames.Length; i++)
        {
            for (int j = 0; j < dt.Rows.Count; j++)
            {
                xlWk.Cells[j + 2, i + 1] = dt.Rows[j][fieldNames[i]].ToString();
            }
        }
    }

无论出于何种原因,在我相对现代的 PC 上转储 25 列和 400 行的 DataTable 大约需要 10-15 秒。需要更长的测试人员机器。

我能做些什么来加快这段代码的速度吗?还是互操作本身就很慢?

解决方案:根据 Helen Toomik 的建议,我修改了该方法,现在它应该适用于几种常见的数据类型(int32、double、datetime、string)。随意扩展它。处理我的数据集的速度从 15 秒降至 1 秒以下。

    private void RenderDataTableOnXlSheet(DataTable dt, Excel.Worksheet xlWk, string [] columnNames, string [] fieldNames)
    {
        Excel.Range rngExcel = null;
        Excel.Range headerRange = null;

        try
        {
            // render the column names (e.g. headers)
            for (int i = 0; i < columnNames.Length; i++)
                xlWk.Cells[1, i + 1] = columnNames[i];

            // for each column, create an array and set the array 
            // to the excel range for that column.
            for (int i = 0; i < fieldNames.Length; i++)
            {
                string[,] clnDataString = new string[dt.Rows.Count, 1];
                int[,] clnDataInt = new int[dt.Rows.Count, 1];
                double[,] clnDataDouble = new double[dt.Rows.Count, 1];

                string columnLetter = char.ConvertFromUtf32("A".ToCharArray()[0] + i);
                rngExcel = xlWk.get_Range(columnLetter + "2", Missing.Value);
                rngExcel = rngExcel.get_Resize(dt.Rows.Count, 1);

                string dataTypeName = dt.Columns[fieldNames[i]].DataType.Name;

                for (int j = 0; j < dt.Rows.Count; j++)
                {
                    if (fieldNames[i].Length > 0)
                    {
                        switch (dataTypeName)
                        {
                            case "Int32":
                                clnDataInt[j, 0] = Convert.ToInt32(dt.Rows[j][fieldNames[i]]);
                                break;
                            case "Double":
                                clnDataDouble[j, 0] = Convert.ToDouble(dt.Rows[j][fieldNames[i]]);
                                break;
                            case "DateTime":
                                if (fieldNames[i].ToLower().Contains("time"))
                                    clnDataString[j, 0] = Convert.ToDateTime(dt.Rows[j][fieldNames[i]]).ToShortTimeString();
                                else if (fieldNames[i].ToLower().Contains("date"))
                                    clnDataString[j, 0] = Convert.ToDateTime(dt.Rows[j][fieldNames[i]]).ToShortDateString();
                                else 
                                    clnDataString[j, 0] = Convert.ToDateTime(dt.Rows[j][fieldNames[i]]).ToString();

                                break;
                            default:
                                clnDataString[j, 0] = dt.Rows[j][fieldNames[i]].ToString();
                                break;
                        }
                    }
                    else
                        clnDataString[j, 0] = string.Empty;
                }

                // set values in the sheet wholesale.
                if (dataTypeName == "Int32") 
                    rngExcel.set_Value(Missing.Value, clnDataInt);
                else if (dataTypeName == "Double")
                    rngExcel.set_Value(Missing.Value, clnDataDouble);                             
                else
                    rngExcel.set_Value(Missing.Value, clnDataString);
            }


            // figure out the letter of the last column (supports 1 letter column names)
            string lastColumn = char.ConvertFromUtf32("A".ToCharArray()[0] + columnNames.Length - 1);

            // make the header range bold
            headerRange = xlWk.get_Range("A1", lastColumn + "1");
            headerRange.Font.Bold = true;

            // autofit for better view
            xlWk.Columns.AutoFit();

        }
        finally
        {
            ReleaseObject(headerRange);
            ReleaseObject(rngExcel);
        }
    }

    private void ReleaseObject(object obj)
    {
        try
        {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
            obj = null;
        }
        catch
        {
            obj = null;
        }
        finally
        {
            GC.Collect();
        }
    }

【问题讨论】:

  • 我知道你已经有了答案,但我想分享我在帖子中提到的图书馆的链接。我用链接更新了帖子。 npoi.codeplex.com
  • @Chris 谢谢。看起来很有趣。
  • 这很好。我唯一推荐的是将类型比较的东西放在行的 for 循环之外。这意味着您必须为每种类型都有一个循环,但是您不必比较每一行的类型。但无论哪种方式,都非常有帮助。

标签: c# performance excel interop .net-2.0


【解决方案1】:

互操作本质上非常慢。 每次调用都会产生很大的开销。 为了加快速度,请尝试在一个赋值语句中将一个对象数组数据写回到一系列单元格中。

或者,如果这是一个严重的问题,请尝试使用托管代码 Excel 扩展程序之一,该扩展程序可以通过 XLL 接口使用托管代码读取/写入数据。 (Addin Express、托管 XLL 等)

【讨论】:

    【解决方案2】:

    我同意查尔斯的观点。互操作真的很慢。但是试试这个:

    private void RenderDataTableOnXlSheet(DataTable dt, Excel.Worksheet xlWk, 
                                        string [] columnNames, string [] fieldNames)
    {
        // render the column names (e.g. headers)
        int columnLength = columnNames.Length;
        for (int i = 0; i < columnLength; i++)
            xlWk.Cells[1, i + 1] = columnNames[i];
    
        // render the data 
            int fieldLength = fieldNames.Length;
            int rowCount = dt.Rows.Count;
            for (int j = 0; j < rowCount; j++)
            { 
                for (int i = 0; i < fieldLength; i++)
                {
                    xlWk.Cells[j + 2, i + 1] = dt.Rows[j][fieldNames[i]].ToString();
                }
            }
    }
    

    HTH

    【讨论】:

    • 这段代码只是改变了跨越DataTable的方向。它并没有提高速度。
    【解决方案3】:

    您对走 COM 自动化路线有特定要求吗?如果没有,您还有其他一些选择。

    1. 使用 OLEDB 提供程序创建/写入 Excel 文件
      http://support.microsoft.com/kb/316934

    2. 使用第三方库写入 Excel。根据您的许可要求,有几个选项。 更新:一个好的免费图书馆是 NPOI http://npoi.codeplex.com/

    3. 将数据写入 csv 文件,并将其加载到 Excel 中

    4. 将数据写入可加载到 Excel 中的 XML。

    5. 使用 Open XML SDK
      http://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en

    【讨论】:

    • 所有好的建议。我不能走 #1 或 #3 路线,因为我正在写多张纸,而且我必须进行格式化。 #4 和 #5 不能这样做,因为用户有 Office 2003。#2 是可能的。
    • 使用 OLEDB 您可以写入多张纸。 sheet.codeproject.com/KB/miscctrl/Excel_data_access.aspx 明天我将更新一个我正在使用的非常好的开源库,但我现在不记得名字了,它提供完全控制、格式化等。
    【解决方案4】:

    如果您有记录集,写入 Excel 的最快方法是 CopyFromRecordset。

    【讨论】:

      【解决方案5】:

      不要一个一个地设置单元格值,而是批量设置。

      步骤 1. 将 DataTable 中的数据传输到具有相同维度的数组中。

      第 2 步。定义一个跨越适当范围的 Excel Range 对象。

      步骤 3. 将 Range.Value 设置为数组。

      这会快很多,因为您将在 Interop 边界上进行总共两次调用(一次用于获取 Range 对象,一次用于设置其值),而不是每个单元格两次(获取单元格,设置值)。

      MSDN KB article 302096 有一些示例代码。

      【讨论】:

      • 是的,这成功了。谢谢。我将更改问题以反映最终代码,以便其他人可以使用它。
      【解决方案6】:

      您可以创建一个 Excel 插件,使用 VBA 代码来完成所有繁重的数据库工作。从 .NET 开始,您需要做的就是实例化 Excel,添加加载项,然后调用 Excel VBA 例程,将执行 SQL 语句所需的任何参数传递给它。

      【讨论】:

      • 在此处添加有关如何实现该目标的更多信息可能会有所帮助,或者至少在示例中链接。我认为您的回答很有用,但我怀疑您因为有点简洁而被否决了。
      【解决方案7】:

      Interop 有最快的方法,称为 CopyFromRecordset 但必须使用 ADODB 库

      绝对是最快的方式/方法,我已经尝试了一些。也许,不好用,但速度惊人:

      https://docs.microsoft.com/en-us/office/vba/api/excel.range.copyfromrecordset

      一个简短的示例:

      using ADODB;
      using Microsoft.Office.Interop;
      
      //--- datatable --- already exists
      DataTable dt_data = new DataTable();
      //--- or your dt code is here ..........
      
      
      //--- mine has 3 columns ------
      
      //--- code to populate ADO rs with DataTable data --- nothing special
      //--- create empty rs .....
      ADODB.Recordset rs = new ADODB.Recordset();
      rs.CursorType = CursorTypeEnum.adOpenKeyset;
      rs.CursorLocation = CursorLocationEnum.adUseClient;
      rs.LockType = LockTypeEnum.adLockOptimistic;
      rs.Fields.Append("employee_id",DataTypeEnum.adBSTR,255,FieldAttributeEnum.adFldIsNullable);
      rs.Fields.Append("full_name", DataTypeEnum.adBSTR, 255, FieldAttributeEnum.adFldIsNullable);
      rs.Fields.Append("start_date", DataTypeEnum.adBSTR, 10, FieldAttributeEnum.adFldIsNullable);
      rs.Open();
      
      //--- populate ADO rs with DataTable data ----    
      for (int i = 0; i < dt_data.Rows.Count; i++)
      {
          rs.AddNew();
          rs.Fields["employee_id"].Value = dt_data.Rows[i]["employee_id"].ToString();
          rs.Fields["full_name"].Value = dt_data.Rows[i]["full_name"].ToString();
          //--- if date is empty......
          if (dt_data.Rows[i]["start_date"].ToString().Length > 0)
          {
              rs.Fields["start_date"].Value = dt_data.Rows[i]["start_date"].ToString();
          }
          rs.Update();
      }
      
      Microsoft.Office.Interop.Excel.Application xlexcel;
      Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
      Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
      object misValue = System.Reflection.Missing.Value;
      xlexcel = new Microsoft.Office.Interop.Excel.Application();
      xlexcel.Visible = true;
      
      
      xlWorkBook = xlexcel.Workbooks.Add(misValue);
      xlWorkSheet = (Microsoft.Office.Interop.Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
      
      //--- populate columns from rs --
      for (int i = 0; i < rs.Fields.Count; i++)
      {
          xlWorkSheet.Cells[1, i + 1] = rs.Fields[i].Name.ToString();
      };
      
      //----- .CopyFromRecordset method -- (rs object, MaxRows, MaxColumns) --- in this case 3 columns but it can 1,2,3 etc ------
      xlWorkSheet.Cells[2, 1].CopyFromRecordset(CloneFilteredRecordset(rs), rs.RecordCount, 3);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-10-26
        • 2016-04-28
        • 1970-01-01
        • 1970-01-01
        • 2011-09-23
        • 2018-03-04
        • 1970-01-01
        相关资源
        最近更新 更多