Как ускорить дамп DataTable на лист Excel? - PullRequest
14 голосов
/ 22 апреля 2010

У меня есть следующая подпрограмма, которая выгружает 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 секунд на моем относительно современном ПК.Занимает еще больше машин тестеров.

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

РЕШЕНИЕ: Основываясь на предложениях Хелен Тоомик, я изменил метод, и теперь он должен работать для нескольких общих типов данных (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();
        }
    }

Ответы [ 6 ]

25 голосов
/ 22 апреля 2010

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

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

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

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

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

Пример кода: MSDN KB, статья 302096 .

3 голосов
/ 22 апреля 2010

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

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

2 голосов
/ 22 апреля 2010

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

1 голос
/ 22 апреля 2010

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

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

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

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

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

  5. Использование Open XML SDKhttp://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en

0 голосов
/ 20 февраля 2014

Вы можете создать надстройку для Excel с кодом VBA, чтобы выполнить всю тяжелую работу с БД. из .NET все, что вам нужно сделать, это создать экземпляр Excel, добавить надстройку и вызвать подпрограмму Excel VBA, передав ей любые параметры, необходимые для выполнения ваших операторов SQL.

0 голосов
/ 22 апреля 2010

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

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

НТН

...