Как ускорить дамп DataTable в рабочий лист Excel?

У меня есть следующая стандартная программа, которая выводит 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();
            }
        }
    }

По любой причине, выводя DataTable 25 столбцов и 400 строк занимает приблизительно 10-15 секунд на моем относительно современном ПК. Берет машины еще более длинных тестеров.

Есть ли что-нибудь, что я могу сделать для ускорения этого кода? Или interop является просто по сути медленным?

РЕШЕНИЕ: На основе предложений от Helen Toomik я изменил метод, и он должен теперь работать на несколько типов общих данных (int32, дважды, дата и время, строка). Не стесняйтесь расширять его. Скорость для обработки моего набора данных перешла с 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();
        }
    }
14
задан AngryHacker 22 April 2010 в 23:05
поделиться

5 ответов

Вместо того, чтобы устанавливать значения ячеек один за другим , сделайте это партиями.

Шаг 1. Перенесите данные из DataTable в массив с такими же размерами.

Шаг 2. Определите объект Excel Range, охватывающий соответствующий диапазон.

Шаг 3. Установите Range.Value в массив.

Это будет намного быстрее, потому что у вас будет всего два вызова через границу взаимодействия (один для получения объекта Range, другой для установки его значения) вместо двух на ячейку (получение ячейки, установка значения).

Пример кода можно найти в статье 302096 базы знаний MSDN .

25
ответ дан 1 December 2019 в 09:12
поделиться

Я согласен с Чарльзом. Взаимодействие действительно медленное. Но попробуйте следующее:

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

-2
ответ дан 1 December 2019 в 09:12
поделиться

Есть ли у вас особые требования для перехода по маршруту автоматизации COM? Если нет, у вас есть несколько других вариантов.

  1. Используйте поставщика OLEDB для создания / записи в файл Excel
    http://support.microsoft.com/kb/316934

  2. Используйте стороннюю библиотеку для записи в Excel. В зависимости от ваших лицензионных требований есть несколько вариантов. Обновление: хорошая бесплатная библиотека - NPOI http://npoi.codeplex.com/

  3. Записать данные в файл csv и загрузить что в Excel

  4. Запишите данные как XML, которые можно загрузить в Excel.

  5. Используйте Open XML SDK
    http://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en

1
ответ дан 1 December 2019 в 09:12
поделиться

Взаимодействие по своей природе очень медленное. С каждым вызовом связаны большие накладные расходы. Чтобы ускорить его, попробуйте записать обратно объектный массив данных в диапазон ячеек в одном операторе присваивания.

Или, если это серьезная проблема, попробуйте использовать одно из расширений Managed Code Excel, которое может читать / записывать данные с помощью управляемого кода через интерфейс XLL. (Addin Express, Managed XLL и т. Д.)

3
ответ дан 1 December 2019 в 09:12
поделиться

Если у вас есть набор записей, самый быстрый способ записи в Excel - это CopyFromRecordset.

2
ответ дан 1 December 2019 в 09:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: