C# .NET - Export DataTable to Excel

Asked By Stelios Lazaris on 25-Feb-03 12:47 PM
Hello everyone,
My name is Stelios Lazaris and I am a postgraduate student in Greece at the university of Patras. I recently read an article about Dynamic ASP.NET Excel Workbooks in C by Peter Bromberg. 
I followed his guidelines and managed to exit from Excel successfully. My problem is that I have written a class that takes as input a DataTable and writes it on a Excel Workbook.
In the initial version I didn't use the Excel.Range object and Excel disappeared from Task Manager as one would expect. But when I used the Excel.Range object, Excel refuses to close. I also noticed that the zombie Excel process terminates only if I execute the same page again, but this still leaves a new Excel process unterminated. So, I always have a zombie Excel process in my system.
I also tried using Excel.Range objects to create a Workbook with hardcoded data and it worked fine. So, I'm not sure if DataTable or Range is the problem. Can you please help me out. I also include the problematic code attached.
Stelios Lazaris
----------------------------------------------------------------------------------------
<pre>
static private void ExportToExcel(string Filename,System.Web.UI.Page page,System.Data.DataTable DT,string[] colNames)
{
int rowIndex=MaxRowsPerSheet;
int colIndex=1;
int sheetIndex=0;
System.Threading.Thread.CurrentThread.CurrentCulture=System.Globalization.CultureInfo.CreateSpecificCulture("en-US");
Excel.Application oExcel=new Excel.Application();
oExcel.SheetsInNewWorkbook=1;
Excel.Workbook WB = (Excel.Workbook)oExcel.Workbooks.Add(Missing.Value);
Excel.Worksheet sheet=null;
Excel.Range range=null;
foreach(DataRow row in DT.Rows)
{
if (rowIndex==MaxRowsPerSheet)
{
rowIndex=1;
colIndex=1;
sheetIndex++;
if (sheet!=null)
sheet=(Excel.Worksheet)WB.Worksheets.Add(Missing.Value,sheet,Missing.Value,Missing.Value);
else
sheet=(Excel.Worksheet)WB.Worksheets["Sheet1"];
if (colNames==null)
{
foreach(DataColumn col in DT.Columns)
{
range=(Excel.Range)sheet.Cells[rowIndex,colIndex++];
range.Value2=col.ColumnName.ToString();
}
}
else
{
foreach(string str in colNames)
{
range=(Excel.Range)sheet.Cells[rowIndex,colIndex++];
range.Value2=str.ToString();
}
}
rowIndex++;
sheet.Name="Page "+(sheetIndex);
}
colIndex=1;
foreach(object item in row.ItemArray)
{
range=(Excel.Range)sheet.Cells[rowIndex,colIndex];
range.Value2=item.ToString();
if (item.ToString().Length>11 && range.NumberFormat!="#")
{
bool IsPhoneNumber=true;
string strItem=item.ToString();
for(int i=0;i {
if (!Char.IsDigit(strItem,i))
{
IsPhoneNumber=false;
break;
}
}
if (IsPhoneNumber)
range.EntireColumn.NumberFormat="#";
}
colIndex++;
}
rowIndex++;
}
for(int i=1;i<=WB.Worksheets.Count;i++)
{
sheet=(Excel.Worksheet)WB.Worksheets["Page "+i];
int num_cols=DT.Columns.Count;
for(int j=1;j<=num_cols;j++)
{
range=(Excel.Range)sheet.Cells[1,j];
range=range.EntireColumn;
range.AutoFit();
range.HorizontalAlignment=Excel.XlHAlign.xlHAlignRight;
}
}
sheet=((Excel.Worksheet)WB.Worksheets["Page 1"]);
sheet.Activate();
string filename=page.Server.MapPath("/APP_TPH_local/temp")+"\\"+page.Session.SessionID+".tmp";
WB.SaveAs(filename,Excel.XlFileFormat.xlWorkbookNormal,Missing.Value,Missing.Value,Missing.Value,Missing.Value,Excel.XlSaveAsAccessMode.xlExclusive,Excel.XlSaveConflictResolution.xlLocalSessionChanges,Missing.Value,Missing.Value,Missing.Value,Missing.Value);
WB.Close(Missing.Value,Missing.Value,Missing.Value);
oExcel.Workbooks.Close();
oExcel.Quit();
System.IO.FileStream input=new System.IO.FileStream(filename,System.IO.FileMode.Open);
System.IO.Stream output=res.OutputStream;
int len=(int)input.Length;
byte[] buffer=new byte[len];
input.Read(buffer,0,len);
output.Write(buffer,0,len);
input.Close();
output.Close();
System.IO.File.Delete(filename);
DT.Dispose();
DT=null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(range);
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(WB);
while(System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel)>0);
sheet=null;
WB=null;
oExcel=null;
range=null;
GC.Collect();
}
</pre>

I see you have the following objects

Asked By Peter Bromberg on 25-Feb-03 03:50 PM
that are instantiated:
oExcel
WB
range
sheet
-- and at the end ---
System.Runtime.InteropServices.Marshal.ReleaseComObject(range); 
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheet); 
System.Runtime.InteropServices.Marshal.ReleaseComObject(WB); 
while(System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel)>0); 
-- so it looks like you have covered everything.
So something about the Excel.Range object is not getting closed out.
That's where I would look to see if there is anything else you can kill.
Sorry to be so imprecise, but its very unpredictable and you just have to 
keep trying. That's the legacy from COM, I guess...

It must be the DataTable's fault

Asked By Stelios Lazaris on 26-Feb-03 05:12 AM
I did some extra checks and have come to the conclusion that the problem lies in the DataTable. When I query the database with a SqlDataReader, Excel terminates normally, but when I switch to SqlDataAdapter and DataSet-DataTable, Excel remains open.
Using Microsoft Office XP is a much better solution as it is faster, lighter (doesn't execute a .exe) but the problem is that it exports SpreadSheet XML which is only readable by Excel 2002.

Found a fairly good solution

Asked By Stelios Lazaris on 26-Feb-03 06:27 AM
Finally, I came up with a solution that is fairly good.
I first use Office XP Web Components to create the Excel Worksheet and apply all the necessary formatting. Then I check if my spreadsheet is multisheet or not.
If it is not multisheet, I export the Spreadsheet as HTML Spreadsheet, which is readable by both Office 2000 and XP.
If it is multisheet, I export the Spreadsheet as Spreadsheet XML which is readable only by Office XP. Then I create a Excel 2002 Application on the server, open the SpreadSheet XML (by OpenXML()) and save it as a Excel Normal Workbook. So using Excel I save the Spreadsheet XML in Excel binary format which is compatible with Office 2000 also.
In this case Excel terminates properly because I instantiate the object inside its own method without the presence of a DataTable because I have already created the Spreadsheet XML file and disposed the DataTable.
Here is the code:
----------------------------------------------------------------------------------------------------
		static private void ExportToExcel(string Filename,System.Web.UI.Page page,System.Data.DataTable DT,string[] colNames)
		{
			System.Web.HttpResponse res=page.Response;
			res.ContentType="application/vnd.ms-excel";
			res.AppendHeader("content-disposition", "attachment; filename="+Filename);
			res.Charset="";
			int num_rows=DT.Rows.Count;
//MaxRowsPerSheet must be the maximum number of rows plus 1
			int MaxRowsPerSheet=64001;
			int rowIndex=MaxRowsPerSheet;
			int colIndex=1;
			int sheetIndex=0;
			System.Threading.Thread.CurrentThread.CurrentCulture=System.Globalization.CultureInfo.CreateSpecificCulture("en-US");
			OWC10.Spreadsheet oExcel=new OWC10.Spreadsheet();
			OWC10.Workbook WB=oExcel.ActiveWorkbook;
			OWC10.Worksheet sheet=null;
			OWC10._Range range=null;
			while(WB.Worksheets.Count>1)
				((OWC10.Worksheet)WB.Worksheets["Sheet"+WB.Worksheets.Count]).Delete();
			foreach(DataRow row in DT.Rows)
			{
				if (rowIndex==MaxRowsPerSheet)
				{
					rowIndex=1;
					colIndex=1;
					sheetIndex++;
					if (sheet!=null)
						sheet=(OWC10.Worksheet)WB.Worksheets.Add(Missing.Value,sheet,Missing.Value,Missing.Value);
					else
						sheet=oExcel.ActiveSheet;
					if (colNames==null)
					{
						foreach(DataColumn col in DT.Columns)
						{
							sheet.Cells[rowIndex,colIndex++]=col.ColumnName.ToString();
						}
					}
					else
					{
						foreach(string str in colNames)
						{
							sheet.Cells[rowIndex,colIndex++]=str.ToString();
						}
					}
					rowIndex++;
					sheet.Name="Sheet"+(sheetIndex);
				}
				colIndex=1;
				foreach(object item in row.ItemArray)
				{
					sheet.Cells[rowIndex,colIndex]=item.ToString();
					range=(OWC10._Range)sheet.Cells[rowIndex,colIndex];
					if (item.ToString().Length>11 && range.get_NumberFormat().ToString()!="#")
					{
						bool IsPhoneNumber=true;
						string strItem=item.ToString();
						for(int i=0;i<strItem.Length;i++)
						{
							if (!Char.IsDigit(strItem,i))
							{
								IsPhoneNumber=false;
								break;
							}
						}
						if (IsPhoneNumber)
							range.EntireColumn.set_NumberFormat("#");
					}
					colIndex++;
				}
				rowIndex++;
			}
			for(int i=1;i<=WB.Worksheets.Count;i++)
			{
				sheet=(OWC10.Worksheet)WB.Worksheets["Sheet"+i];
				int num_cols=DT.Columns.Count;
				for(int j=1;j<=num_cols;j++)
				{
					range=(OWC10.Range)sheet.Cells[1,j];
					range=range.EntireColumn;
					range.AutoFit();
					range.set_HorizontalAlignment(OWC10.XlHAlign.xlHAlignRight);
				}
			}
			((OWC10.Worksheet)oExcel.Worksheets["Sheet1"]).Activate();
			string filename=page.Server.MapPath("/APP_TPH_local/temp")+"\\"+page.Session.SessionID;
			if (DT.Rows.Count<MaxRowsPerSheet)
			{
				oExcel.Export(filename+".xls",OWC10.SheetExportActionEnum.ssExportActionNone,OWC10.SheetExportFormat.ssExportHTML);
			}
			else
			{
				oExcel.Export(filename+".xml",OWC10.SheetExportActionEnum.ssExportActionNone,OWC10.SheetExportFormat.ssExportAsAppropriate);
				ReturnExcelFile(filename);
				System.IO.File.Delete(filename+".xml");
			}
			oExcel=null;
			System.IO.FileStream input=new System.IO.FileStream(filename+".xls",System.IO.FileMode.Open);
			System.IO.Stream output=res.OutputStream;
			int len=(int)input.Length;
			byte[] buffer=new byte[len];
			input.Read(buffer,0,len);
			output.Write(buffer,0,len);
			input.Close();
			output.Close();
			System.IO.File.Delete(filename+".xls");
		}
		private static void ReturnExcelFile(string filename)
		{
			Excel._Application ExcelApp=new Excel.Application();
			Excel._Workbook ExcelWB=ExcelApp.Workbooks.OpenXML(filename+".xml",Missing.Value);
			ExcelWB.SaveAs(filename+".xls",Excel.XlFileFormat.xlWorkbookNormal,Missing.Value,Missing.Value,Missing.Value,Missing.Value,Excel.XlSaveAsAccessMode.xlExclusive,Missing.Value,Missing.Value,Missing.Value,Missing.Value,Missing.Value);
			ExcelWB.Close(Missing.Value,Missing.Value,Missing.Value);
			ExcelApp.Workbooks.Close();
			ExcelApp.Quit();
			System.Runtime.InteropServices.Marshal.ReleaseComObject(ExcelWB);
			System.Runtime.InteropServices.Marshal.ReleaseComObject(ExcelApp);
			ExcelWB=null;
			ExcelApp=null;
			GC.Collect();
			GC.WaitForPendingFinalizers();
		}
If i am not mistaken,
Asked By Peter Bromberg on 26-Feb-03 08:50 AM
the OWC spreadsheet component gives you a choice of formats for exporting
that are backwards compatible ...
thankyou stelios!!
Asked By hugh doar on 17-Feb-04 09:16 AM
owc9 does *not* support multi-sheet, and owc10 exports only as nasty HTML or nice XML, but indeed orifice2000 doesn't read XML. tearing hair out over this until found your automation routine to convert XML to XLS - lovely.. thankyou very much!!
Cika Pero replied to Stelios Lazaris on 19-Feb-10 10:16 AM
Hi

if you are working with small data amounts, try free version of GemBox.Spreadsheet http://www.gemboxsoftware.com/GBSpreadsheet.htm component.

It supports http://www.gemboxsoftware.com/LA/Import-Export-DataTable-XLS-XLSX-CSV-HTML-.NET.htm within just one method call. Very easy to use!