Подтвердить что ты не робот

Как ускорить сброс 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 просто изначально медленный?

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

Ответ 1

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

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

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

Шаг 3. Задайте Range.Value массиву.

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

Существует несколько примеров кода статья MSDN KB 302096.

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

Ответ 6

Я согласен с Чарльзом. 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();
            }
        }
}

НТН