У меня есть следующая стандартная программа, которая выводит 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();
}
}
Вместо того, чтобы устанавливать значения ячеек один за другим , сделайте это партиями.
Шаг 1. Перенесите данные из DataTable в массив с такими же размерами.
Шаг 2. Определите объект Excel Range, охватывающий соответствующий диапазон.
Шаг 3. Установите Range.Value в массив.
Это будет намного быстрее, потому что у вас будет всего два вызова через границу взаимодействия (один для получения объекта Range, другой для установки его значения) вместо двух на ячейку (получение ячейки, установка значения).
Пример кода можно найти в статье 302096 базы знаний MSDN .
Я согласен с Чарльзом. Взаимодействие действительно медленное. Но попробуйте следующее:
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
Есть ли у вас особые требования для перехода по маршруту автоматизации COM? Если нет, у вас есть несколько других вариантов.
Используйте поставщика OLEDB для создания / записи в файл Excel
http://support.microsoft.com/kb/316934
Используйте стороннюю библиотеку для записи в Excel. В зависимости от ваших лицензионных требований есть несколько вариантов. Обновление: хорошая бесплатная библиотека - NPOI http://npoi.codeplex.com/
Записать данные в файл csv и загрузить что в Excel
Запишите данные как XML, которые можно загрузить в Excel.
Используйте Open XML SDK
http://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en
Взаимодействие по своей природе очень медленное. С каждым вызовом связаны большие накладные расходы. Чтобы ускорить его, попробуйте записать обратно объектный массив данных в диапазон ячеек в одном операторе присваивания.
Или, если это серьезная проблема, попробуйте использовать одно из расширений Managed Code Excel, которое может читать / записывать данные с помощью управляемого кода через интерфейс XLL. (Addin Express, Managed XLL и т. Д.)
Если у вас есть набор записей, самый быстрый способ записи в Excel - это CopyFromRecordset.