如何加快将DataTable转储到Excel工作表?

时间:2010-04-22 17:18:30

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

我有以下例程将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();
        }
    }

7 个答案:

答案 0 :(得分:25)

不是逐个设置单元格值,而是批量执行。

步骤1.将数据从DataTable传输到具有相同尺寸的数组中。

步骤2.定义跨越适当范围的Excel范围对象。

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

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

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

答案 1 :(得分:3)

Interop本来就很慢。 每次调用都会产生很大的开销。 为了加快速度,尝试在一个赋值语句中将对象数组数据写回一系列单元格。

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

您是否有特定要求进入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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

答案 6 :(得分:-2)

我同意查尔斯。 Interop真的很慢。但试试这个:

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