Excel export in normal text format - c#

I currently am writing a system that includes reading in excel files. I want to read in this excel file and potentially spew it out into a csv. However, the issue im running into is that it is keeping the format that excel uses. For example i have a long number that in excel displays as 3.9151+E15, and it reads it in like this. When i highlight the cell in excel it shows the real number '3915100000026840'. This is the number i want to receive. It also adds a timestamp to dates which i do not want. It adds 00:00 00:00:000 or something similar to 17/05/2018, which is all i want. So basically, i want to retrieve the real text values from this excel spreadsheet.
The code i have at the minute is
public static DataTable READExcel(string path)
{
Microsoft.Office.Interop.Excel.Application objXL = null;
Microsoft.Office.Interop.Excel.Workbook objWB = null;
objXL = new Microsoft.Office.Interop.Excel.Application();
objWB = objXL.Workbooks.Open(path);
Microsoft.Office.Interop.Excel.Worksheet objSHT = objWB.Worksheets[1];
int rows = objSHT.UsedRange.Rows.Count;
int cols = objSHT.UsedRange.Columns.Count;
DataTable dt = new DataTable();
int noofrow = 1;
for (int c = 1; c <= cols; c++)
{
string colname = objSHT.Cells[1, c].Value.ToString();
dt.Columns.Add(colname);
noofrow = 2;
}
for (int r = noofrow; r <= rows; r++)
{
DataRow dr = dt.NewRow();
for (int c = 1; c <= cols; c++)
{
dr[c - 1] = objSHT.Cells[r, c].Value.ToString();
}
dt.Rows.Add(dr);
}
objWB.Close();
objXL.Quit();
return dt;
}
(Also another question which is slightly related but slightly not related, i had a csv to start which had the value '0003915100000026845'. When i turned this csv into a excel file it changed it to the value i reference above. Will excel have remembered these leading zeros anywhere or not?)

Related

Export selected rows from datagridview to excel using spire.XLS & C#

I'm trying to export the manually selected rows of a datagridview on a windows form application to a excel file using spire.XLS and add the sum of the last column in the last row. The datagridview data is like below:
And when I run the code after selecting the 1st & the last rows then the excel should look like:
Here is my code:
void ExportBtnXLSXClick(object sender, EventArgs e)
{
Workbook book = new Workbook();
Worksheet sheet = book.Worksheets[0];
sheet.Name = "Exported from gridview";
//Convert data from datagridview to datatable
DataTable dt=GetDgvToTable(dataGridView1);
//Export datatable to excel
sheet.InsertDataTable(dt, true, 1, 1, -1, -1);
//sheet.InsertDataTable(dt, true, 1, 1, true);
sheet.Range[1,1,sheet.LastRow,sheet.LastColumn].AutoFitColumns();
sheet.AllocatedRange.BorderAround(LineStyleType.Thin, borderColor:ExcelColors.Black);
sheet.AllocatedRange.BorderInside(LineStyleType.Thin, borderColor:ExcelColors.Black);
book.SaveToFile(#"C:\Users\Tamal\Desktop\Spire.XLS C#\Report.xlsx", ExcelVersion.Version2013);
book.Dispose();
MessageBox.Show("Export complete");
}
with the helper method
public DataTable GetDgvToTable(DataGridView dgv)
{
DataTable dt = new DataTable();
//Column
for (int count = 0; count < dgv.Columns.Count; count++)
{
DataColumn dc = new DataColumn(dgv.Columns[count].Name.ToString());
dt.Columns.Add(dc);
}
//Row
for (int count = 0; count < dgv.SelectedRows.Count; count++)
{
DataRow dr = dt.NewRow();
for (int countsub = 0; countsub < dgv.Columns.Count; countsub++)
//for (int countsub = 0; countsub < dgv.SelectedRows.Count; countsub++)
{
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
}
dt.Rows.Add(dr);
}
decimal total = dataGridView1.SelectedRows.OfType<DataGridViewRow>()
.Sum(t => Convert.ToDecimal(t.Cells[2].Value));
dt.Rows.Add(total);
return dt;
}
But it is not showing the two rows that I selected and also the sum is showing on the 1st column of the last row in excel. How can I get the desired result?
Also, if I run the code multiple times then the data should not overwrite but just paste data after the last filled row, preferably keeping a row blank in between.
Here is how the datagridview looks like:
Please help
You have multiple questions here. For starters, if you want to add multiple selections to the same workbook, then you will need to do some things differently…
check to see if the file already exists, if not, then create a new one, if it does exist then you will need to “open” it.
If the file already exists, then, after opening the file, you will need to check if the worksheet "Exported from gridview" exists… if it does exist, then, you will need to find the last used row and start adding the additional rows after that last row.
Finally save the file.
Currently, the code is simply “creating” a new workbook, then the code is overwriting the existing workbook if it already exists. So, if you want to “add” additional selections to the “same” workbook, you will need to add the code that checks if the file exists, and if so, find the next available row on the worksheet to add the additional items to as explained in the previous 3 steps above.
Below is a simple example that works however it was not tested well and I am confident you may need to adjust the code to fit your requirements.
First, in the GetDgvToTable method, it appears the code is grabbing the wrong rows when creating the table….
for (int count = 0; count < dgv.SelectedRows.Count; count++)
{
DataRow dr = dt.NewRow();
for (int countsub = 0; countsub < dgv.Columns.Count; countsub++)
//for (int countsub = 0; countsub < dgv.SelectedRows.Count; countsub++)
{
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
}
dt.Rows.Add(dr);
}
Above, the code is looping through the “number of selected” rows. The problem is on the line…
dr[countsub] = Convert.ToString(dgv.Rows[count].Cells[countsub].Value);
Specifically, at …
dgv.Rows[count].
Here the code is ignoring the “SELECTED” rows. In other words, if the “selected” rows were contiguous “selected” rows starting from the “first” row (0), then it will work. Unfortunately, the first “selected” row may not necessarily be the “first” row in the grid. The code is incorrectly making this assumption.
To fix this, I suggest you loop through the “selected” rows using a foreach loop through grids SelectedRows collection. This will ensure the code uses the “SELECTED” rows. The code change would look something like…
foreach (DataGridViewRow row in dgv.SelectedRows) {
DataRow dr = dt.NewRow();
for (int i = 0; i < row.Cells.Count; i++) {
dr[i] = row.Cells[i].Value.ToString();
}
dt.Rows.Add(dr);
}
Next, you state you want to additional selections to be added to the same worksheet with an empty row between the selections. This was previously discussed above. In the code below, it checks to see if the Excel file already exist and if it does, then opens that file. Next a check is made to see if the worksheet already exists. If it does not exist, then a new one is created.
Next a check is made to see if any “previous” selections have already been added to the worksheet. I am not that familiar with Spire, however, I noted that if you call the method…
sheet.LastRow…
It will return the next available row… EXCEPT if the worksheet is empty. When the worksheet is empty, this will return a value like 65,570. I will leave this to you to possibly figure out a more elegant way to get the next empty row. In this case I simply checked if the sheet.LastRow was over 60000. If the value is over 60000, then I assume the sheet is empty and simply set the next available row to 1. I am confident there is a better way to do this.
Given all this, the changes below to both methods appears to do as you want by adding the additional selection to the same worksheet “below” the previous selections.
public DataTable GetDgvToTable(DataGridView dgv) {
DataTable dt = new DataTable();
//Column
for (int count = 0; count < dgv.Columns.Count; count++) {
DataColumn dc = new DataColumn(dgv.Columns[count].Name.ToString());
dt.Columns.Add(dc);
}
//Row
foreach (DataGridViewRow row in dgv.SelectedRows) {
DataRow dr = dt.NewRow();
for (int i = 0; i < row.Cells.Count; i++) {
dr[i] = row.Cells[i].Value.ToString();
}
dt.Rows.Add(dr);
}
decimal total = dataGridView1.SelectedRows.OfType<DataGridViewRow>()
.Sum(t => Convert.ToDecimal(t.Cells[2].Value));
dt.Rows.Add("","Total",total);
return dt;
}
Then the changes to the Export method.
private void btnExportToExcel_Click(object sender, EventArgs e) {
Workbook book = new Workbook();
if (File.Exists(#"pathToFile\Report.xlsx")) {
book.LoadFromFile(#"pathToFile\Report.xlsx");
}
Worksheet sheet = book.Worksheets["Exported from gridview"];
if (sheet == null) {
sheet = book.CreateEmptySheet("Exported from gridview");
}
//Convert data from datagridview to datatable
DataTable dt = GetDgvToTable(dataGridView1);
//Export datatable to excel
int startRow = sheet.LastRow + 2;
if (startRow > 60000) {
startRow = 1;
}
sheet.InsertDataTable(dt, true, startRow, 1, -1, -1);
sheet.Range[1, 1, sheet.LastRow, sheet.LastColumn].AutoFitColumns();
sheet.AllocatedRange.BorderAround(LineStyleType.Thin, borderColor: ExcelColors.Black);
sheet.AllocatedRange.BorderInside(LineStyleType.Thin, borderColor: ExcelColors.Black);
book.SaveToFile(#"pathToFile\Report.xlsx", ExcelVersion.Version2013);
book.Dispose();
MessageBox.Show("Export complete");
}
I hope this makes sense and helps.

How do I convert a Value2 string to a DateTime (Excel Interop)

I am importing data from an Excel file and converting it into a DataSet, which I have working (kind of). The problem that I am having is that two fields are Dates in Excel but when I do the import they get turned into numbers and I am not sure how to get them back to dates. The last two columns are the columns that are dates (HDate & CDate).
This is the code
private void FillDatagrid()
{
Excel.Application xlApp = new Excel.Application();
Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(#"\\Server01\Ins\Eligible.xls");
Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
Excel.Range xlRange = xlWorksheet.UsedRange;
DataTable table = new DataTable("Employees");
table.Columns.Add("StoreID");
table.Columns.Add("EmpID");
table.Columns.Add("EmpName");
table.Columns.Add("Position");
table.Columns.Add("HDate");
table.Columns.Add("CDate");
int rowCount = xlRange.Rows.Count;
for (int i = 2; i <= rowCount; i++)
{
table.Rows.Add(
xlRange.Cells[i, 1].Value2.ToString(),
xlRange.Cells[i, 2].Value2.ToString(),
xlRange.Cells[i, 3].Value2.ToString(),
xlRange.Cells[i, 4].Value2.ToString(),
xlRange.Cells[i, 5].Value2.ToString(),
xlRange.Cells[i, 5].Value2.ToString());
}
DataSet ds = new DataSet();
ds.Tables.Add(table);
dataGrid.ItemsSource = ds.Tables["Employees"].DefaultView;
}
As far as I can tell a Value2 will only convert to a string.
Note #1 Although I am putting this data into a DataGrid in this sample I am not doing that in the actual code block. I just wanted to mention that so that it didn't seem like this could be fixed with formatting in XAML etc.
Note #2 I realize I have xlRange.Cells[i, 5].Value2.ToString() twice. I am doing so to get around the problem of column 6 have null values, which my import didn't like. I plan to come back to that after I get this problem fixed.
Note #3 When I say I am getting the date as a string I mean it is coming from Excel as a string but formatted as a number so, for instance, the cell data has a date like 6/30/2015 but it comes over to my dataset like 42185.
What about:
table.Columns.Add("HDate", typeof(DateTime));
table.Columns.Add("CDate", typeof(DateTime));
I was able to solve this by altering my loop
for (int i = 2; i <= rowCount; i++)
{
string storeId = xlRange.Cells[i, 1].Value2.ToString();
string employeeId = xlRange.Cells[i, 2].Value2.ToString();
string employeeName = xlRange.Cells[i, 3].Value2.ToString();
string position = xlRange.Cells[i, 4].Value2.ToString();
string hDate = Convert.ToString(xlRange.Cells[i, 5].Value);
string cDate = Convert.ToString(xlRange.Cells[i, 6].Value);
table.Rows.Add(storeId, employeeId, employeeName, position, hDate, cDate);
}
This also took care of another problem. Which is to say the Convert.ToString(xxx.Value) handles nulls where the Value2.ToString() would not.

Storing Excel Rows in List<Range> for later insertion into different workbook

I am splitting one large spreadsheet into many (100's) smaller spreadsheet. My approach is to store the rows of the source spreadsheet in a list:
List<Range> ranges = new List<Range>();
Workbook book = xl.Workbooks.Add("path to book");
Worksheet sheet = book.sheets[1];
for (int r = 1; r <= sheet.UsedRange.Rows.Count; r++)
{
ranges.Add((Range)sheet.Rows[r]);
}
book.Close();
......
Workbook book2 = xl.Workbooks.Add();
Worksheet sheet2 = book2.sheets[1];
for (int r2 = 0; r2 <= ranges.Count; r2++)
{
Range row = (Range)ranges[r2]; //
sheet2.rows[r2+1].Value2 = row; //fails;
//querying in debug, the properties of row all throw an exception
//queryying sheet.rows[r2+1] expands as expected
}
If you see where my error is please advise.
Thanks.
I think after you close the first source book, you can no longer use the range references you've taken from it. So move book.Close() to the end of your snippet. Your upper bound for the for second loop should just be < not <=.
for (int r2 = 0; r2 < ranges.Count; r2++)
{
ranges[r2].Copy();
sheet2.Paste(sheet2.Rows[r2+1]);
}

Excel Work Book - Read from C# substantially slow ?

was experimenting with reading from an excel workbook and noticed it takes a long time to read a sheet with 3560 rows and 7 columns, about 1 minute and 17 seconds. All I did was loop through the whole sheet and store the values in a list.
Is this normal, or am I doing something wrong ?
static void Main(string[] args)
{
List<string> testList = new List<string>();
Excel.Application excelApp = new Excel.Application();
Excel.Workbook workbook = excelApp.Workbooks.Open(#"C:\Users\rnewell\Desktop\FxData.xlsx");
Excel.Worksheet worksheet = workbook.Sheets[1];
Excel.Range range = worksheet.UsedRange;
int rowCount = range.Rows.Count;
int colCount = range.Columns.Count;
int rowCounter = 1;
int colCounter = 1;
while (rowCounter < rowCount)
{
colCounter = 1;
while (colCounter <= colCount)
{
//Console.Write(range.Cells[rowCounter, colCounter].Value2.ToString() + " ");
testList.Add(range.Cells[rowCounter, colCounter].Value2.ToString());
colCounter++;
}
Console.WriteLine();
rowCounter++;
}
Console.ReadKey();
excelApp.Workbooks.Close();
}
#TimWilliams' comment is the correct answer. Reading a single cell takes as long as reading a range of any size. This is the overhead of talking to the COM layer, and you are incurring it thousands of times. You should write the range to an object[,], and then access that array cell by cell.
int rowCount = range.Rows.Count;
int colCount = range.Columns.Count;
object[,] values= range.Value2;
int rowCounter = 1;
int colCounter = 1;
while (rowCounter < rowCount)
{
colCounter = 1;
while (colCounter <= colCount)
{
// check for null?
testList.Add(values[rowCounter, colCounter].ToString());
}
}
Note that the array will be one-based instead of zero-based like normal C# arrays. The data will go from 1 to rowCount and from 1 to colCount, but Rows and Columns properties will return rowCount and colCount, not 1 + rowCount and 1 + colCount. If you want to write data back, you can use a zero-based array of the right size (in fact you have to AFAIK since you can't create a one-based array) and it will work fine.
Since you are loading data from the Open XML (*.xlsx) file format, I would suggest you use Open XML SDK. It doesn't start Excel in the background which is always a good thing, in particular if you need to run your code non-interactively.
I've also written a blog post on different methods of accessing data in Excel which you might find useful.
In general, it should be a matter of seconds.
But as you are creating an instance of Excel itself including its addons it may take a long time to initialize everything in your instance.
For your purpose you can use any public domain excel sheet reading library which doesn't launch Excel.

How to speed up dumping a DataTable into an Excel worksheet?

I have the following routine that dumps a DataTable into an Excel worksheet.
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();
}
}
}
For whatever reason, dumping DataTable of 25 columns and 400 rows takes about 10-15 seconds on my relatively modern PC. Takes even longer testers' machines.
Is there anything I can do to speed up this code? Or is interop just inherently slow?
SOLUTION: Based on suggestions from Helen Toomik, I've modified the method and it should now work for several common data types (int32, double, datetime, string). Feel free to extend it. The speed for processing my dataset went from 15 seconds to under 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();
}
}
Instead of setting cell values one by one, do it in a batch.
Step 1. Transfer the data from your DataTable into an array with the same dimensions.
Step 2. Define an Excel Range object that spans the appropriate range.
Step 3. Set the Range.Value to the array.
This will be a lot faster because you will have a total two calls across the Interop boundary (one to get the Range object, one to set its value), instead of two per cell (get cell, set value).
There is some sample code at MSDN KB article 302096.
Interop is inherently very slow.
There is a large overhead associated with each call.
To speed it up try writing back an object array of data to a range of cells in one assignment statement.
Or if this is a serious problem try using one of the Managed Code Excel extensions that can read/write data using managed code via the XLL interface. (Addin Express, Managed XLL etc.)
If you have a recordset, the fastest way to write to Excel is CopyFromRecordset.
Do you have a specific requirement to go the COM automation route? If not, you have a few other options.
Use the OLEDB provider to create/write to an Excel file http://support.microsoft.com/kb/316934
Use a third party library to write to Excel. Depending on your licensing requirements there are a few options.
Update: A good free library is NPOI http://npoi.codeplex.com/
Write the data to a csv file, and load that into Excel
Write the data as XML which can be loaded into Excel.
Use the Open XML SDK
http://www.microsoft.com/downloads/details.aspx?familyid=C6E744E5-36E9-45F5-8D8C-331DF206E0D0&displaylang=en
Interop has the fastest method called CopyFromRecordset
but ADODB library has to be used
Definitely the fastest way/method and I have tried a few. Perhaps, not easy to use but the speed is astonishing:
https://learn.microsoft.com/en-us/office/vba/api/excel.range.copyfromrecordset
a short sample:
using ADODB;
using Microsoft.Office.Interop;
//--- datatable --- already exists
DataTable dt_data = new DataTable();
//--- or your dt code is here ..........
//--- mine has 3 columns ------
//--- code to populate ADO rs with DataTable data --- nothing special
//--- create empty rs .....
ADODB.Recordset rs = new ADODB.Recordset();
rs.CursorType = CursorTypeEnum.adOpenKeyset;
rs.CursorLocation = CursorLocationEnum.adUseClient;
rs.LockType = LockTypeEnum.adLockOptimistic;
rs.Fields.Append("employee_id",DataTypeEnum.adBSTR,255,FieldAttributeEnum.adFldIsNullable);
rs.Fields.Append("full_name", DataTypeEnum.adBSTR, 255, FieldAttributeEnum.adFldIsNullable);
rs.Fields.Append("start_date", DataTypeEnum.adBSTR, 10, FieldAttributeEnum.adFldIsNullable);
rs.Open();
//--- populate ADO rs with DataTable data ----
for (int i = 0; i < dt_data.Rows.Count; i++)
{
rs.AddNew();
rs.Fields["employee_id"].Value = dt_data.Rows[i]["employee_id"].ToString();
rs.Fields["full_name"].Value = dt_data.Rows[i]["full_name"].ToString();
//--- if date is empty......
if (dt_data.Rows[i]["start_date"].ToString().Length > 0)
{
rs.Fields["start_date"].Value = dt_data.Rows[i]["start_date"].ToString();
}
rs.Update();
}
Microsoft.Office.Interop.Excel.Application xlexcel;
Microsoft.Office.Interop.Excel.Workbook xlWorkBook;
Microsoft.Office.Interop.Excel.Worksheet xlWorkSheet;
object misValue = System.Reflection.Missing.Value;
xlexcel = new Microsoft.Office.Interop.Excel.Application();
xlexcel.Visible = true;
xlWorkBook = xlexcel.Workbooks.Add(misValue);
xlWorkSheet = (Microsoft.Office.Interop.Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
//--- populate columns from rs --
for (int i = 0; i < rs.Fields.Count; i++)
{
xlWorkSheet.Cells[1, i + 1] = rs.Fields[i].Name.ToString();
};
//----- .CopyFromRecordset method -- (rs object, MaxRows, MaxColumns) --- in this case 3 columns but it can 1,2,3 etc ------
xlWorkSheet.Cells[2, 1].CopyFromRecordset(CloneFilteredRecordset(rs), rs.RecordCount, 3);
You could create an Excel add-in, with VBA code to do all your db heavy lifting. from .NET, all you'd need to do is instantiate Excel, add the add-in, and call the Excel VBA routine, passing any parameters to it that it needs to execute your SQL statements.
I agree with Charles. Interop is really slow. But try this:
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

Categories