2010-04-22 3 views
12

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

Я знаю, что у вас уже есть ответ, но я хотел поделиться ссылкой на библиотеку, о которой я упоминал в своем сообщении. Я обновил сообщение по ссылке. http://npoi.codeplex.com/ –

+0

@Chris Спасибо. Выглядит интересно. – AngryHacker

+1

Это очень хорошо. Единственное, что я бы рекомендовал, это поместить элементы сравнения типов вне цикла for для строк. Это означало бы, что вам придется иметь цикл для каждого типа, но тогда вам не нужно сравнивать типы для каждой строки. Но в любом случае, очень полезно. – zumalifeguard

ответ

23

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

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

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

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

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

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

+0

Да, это сделал трюк. Благодарю. Я изменю вопрос, чтобы отразить окончательный код, чтобы другие люди могли его использовать. – AngryHacker

2

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

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

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

НТН

+0

Этот код просто изменяет направление охвата DataTable. Это не улучшило скорость. – AngryHacker

1

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

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

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

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

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

  5. использовать открытый XML SDK
    http://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en

+0

Все хорошие предложения. Я не могу идти по маршруту №1 или №3, потому что я пишу несколько листов, и мне нужно делать форматирование. # 4 и # 5 не могут делать, потому что у пользователей есть Office 2003. # 2 - возможность. – AngryHacker

+1

Используя OLEDB, вы можете написать несколько листов. sheets.http: //www.codeproject.com/KB/miscctrl/Excel_data_access.aspx Завтра я буду обновлять с помощью очень хорошей библиотеки с открытым исходным кодом, которую я использую, но я просто не помню это имя прямо сейчас, оно обеспечивает полное управление, форматирование и т. д. –

1

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

0

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

+1

это может помочь добавить немного больше информации о том, как этого достичь, или, по крайней мере, ссылку в примере. Я думаю, что ваш ответ полезен, но я подозреваю, что вы получаете downvotes, потому что это немного немного. –

Смежные вопросы