Click here to Skip to main content
Click here to Skip to main content

Exporting a dataset with multiple tables to separate sheets in an Excel file

By , 23 May 2007
 

Introduction

This article explains how to export the contents of a dataset with more than one table, to an Excel workbook in separate sheets. The article also helps to understand the basics behind the Excel application object and its usage for creating a Workbook and inserting Worksheets in it. The following code can however extend its functionalities by incorporating methods and procedures for formatting the written data like the cell background, font color, inserting formulas, etc.

Background

The need to write this code came up when one of my applications needed the same functionalities where my Stored Procedure returned two result sets which I stored in a dataset. The user wanted the two result sets to be displayed in two separate sheets in an Excel file. But the current method of Response.AddHeader ("content-disposition") allowed me to write the output in one single sheet with the two result sets one below the other. After exploring the net and integration of a couple of techniques, I came up with a solution which could solve my purpose of creating an Excel file and then writing the data from the dataset tables in the Excel file.

Using the Code - Implementation

The code is pretty simple and straightforward. As a pre-requisite, you should have Excel installed on your system. I have used VB.NET as my base language. Although with a little modification you can always convert the existing code to C# too.

In order to start using the code, add a reference to the COM object Microsoft Excel Object Library. Since I had Microsoft Office 2003 installed on my system, it was Microsoft Excel 11.0 Object Library in my case. Now import the namespaces for the Excel library and InteropServices into your code.

Imports Microsoft.Office.Interop
Imports System.Runtime.InteropServices.Marshal

We will need to import InteropServices because the Microsoft Office code is still based on the old, unmanaged world and you need to use COM Interop to facilitate communication with it. Now copy the following code to your code-behind file to export the dataset tables to Excel. The function takes as parameter a DataSet containing the DataTables.

Public Sub ExportToExcel(ByVal DS_MyDataset As DataSet)

    'The full path where the excel file will be stored
    Dim strFileName As String = _
        AppDomain.CurrentDomain.BaseDirectory.Replace("/", "\") 
    strFileName = strFileName & "\MyExcelFile" & _
                  System.DateTime.Now.Ticks.ToString() ".xls"

    Dim objExcel As Excel.Application
    Dim objBooks As Excel.Workbooks, objBook As Excel.Workbook
    Dim objSheets As Excel.Sheets, objSheet As Excel.Worksheet
    Dim objRange As Excel.Range

    Try
        'Creating a new object of the Excel application object
        objExcel = New Excel.Application
        'Hiding the Excel application
        objExcel.Visible = False
        'Hiding all the alert messages occurring during the process
        objExcel.DisplayAlerts = False

        'Adding a collection of Workbooks to the Excel object
        objBook = CType(objExcel.Workbooks.Add(), Excel. Workbook)

        'Saving the Workbook as a normal workbook format.
        objBook.SaveAs(strFileName, Excel.XlFileFormat.xlWorkbookNormal) 

        'Getting the collection of workbooks in an object
        objBooks = objExcel.Workbooks

        'Get the reference to the first sheet
        'in the workbook collection in a variable
        objSheet = CType(objBooks(1).objSheets.Item(1), Excel.Worksheet)
        'Optionally name the worksheet
        objSheet.Name = "First Sheet"
        'You can even set the font attributes of a range of cells
        'in the sheet. Here we have set the fonts to bold.
        objSheet.Range("A1","Z1").Font.Bold = True 

        'Get the cells collection of the sheet in a variable, to write the data.
        objRange = objSheet.Cells

        'Calling the function to write the dataset data in the cells of the first sheet.
        WriteData(DS_MyDataset.Tables(0), objCells)

        'Setting the width of the specified range of cells
        'so as to absolutely fit the written data.
        objSheet.Range("A1","Z1").EntireColumn.AutoFit()
        'Saving the worksheet.
        objSheet.SaveAs(strFileName)
        
        objBook = objBooks.Item(1)
        objSheets = objBook.Worksheets
        objSheet = CType(objSheets.Item(2), Excel.Worksheet)
        objSheet.Name = "Second Sheet"
        'Setting the color of the specified range of cells
        'to Red (ColorIndex 3 denoted Red color)
        objSheet.Range("A1","Z1").Font.ColorIndex = 3

        objRange = objSheet.Cells
        WriteData(DS_MyDataset.Tables(1), objCells)
        objSheet.Range("A1","Z1").EntireColumn.AutoFit()

        objSheet.SaveAs(strFileName)

    Catch ex As Exception
        Response.Write(ex.Message)
    Finally
        'Close the Excel application
        objExcel.Quit()

        'Release all the COM objects so as to free the memory
        ReleaseComObject(objRange)
        ReleaseComObject(objSheet)
        ReleaseComObject(objSheets)
        ReleaseComObject(objBook)
        ReleaseComObject(objBooks)
        ReleaseComObject(objExcel)

        'Set the all the objects for the Garbage collector to collect them.
        objExcel = Nothing 
        objBooks = Nothing 
        objBook = Nothing 
        objSheets = Nothing 
        objSheet = Nothing 
        objRange = Nothing 

        'Specifically call the garbage collector.
        System.GC.Collect()
    End Try
End Sub

Private Function WriteData(ByVal DT_DataTable As DataTable, _
        ByVal objCells As Excel.Range) As String
    Dim iRow As Integer, iCol As Integer

    'Traverse through the DataTable columns to write the
    'headers on the first row of the excel sheet.
    For iCol = 0 To DT_DataTable.Columns.Count - 1
        objCells(1, iCol + 1) = DT_DataTable.Columns(iCol).ToString
    Next

    'Traverse through the rows and columns
    'of the datatable to write the data in the sheet.
    For iRow = 0 To DT_DataTable.Rows.Count - 1
        For iCol = 0 To DT_DataTable.Columns.Count - 1
            objCells(iRow + 2, iCol + 1) = DT_DataTable.Rows(iRow)(iCol)
        Next
    Next
End Function

Pros and Cons

Pros:

  • Code can be used as a component to export formatted reports to Excel.
  • The code is very small and can be modified as per user requirements for getting formatted output on Excel sheets in a presentable format.

Cons:

  • The code uses Excel Object Library which is required at the development server.
  • Since the code creates objects of COM components through Interop services, if the components are not efficiently released, it may result in memory leakage.

Conclusion

You can see that the above code is self explanatory and quite understandable. The code can further be enhanced by formatting the written data like the background of the cells, adding gridlines to the Excel sheet, etc. We can even add more sheets to the file if the dataset contains more than three tables since the default workbook has three sheets. The above code is just a sample of what all can be done with the Excel object. More enhanced exception handling can be done by catching more specific exceptions. Enjoy and happy coding!!!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

jain.ashish21
Web Developer
India India
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberleo.sanni9 Nov '11 - 16:22 
I find this article excellent. This is exactly what i am looking for. Hands off.
GeneralAlternativemembergg423715 Nov '09 - 23:58 
Hi,
 
you could also check out GemBox.Spreadsheet .NET Excel component for reading and writing XLS, XLSX, ODS, CSV and HTML files.
 
Here is an example how easy it is to export/import DataSet to Excel with GemBox.Spreadsheet.
 
// Create new ExcelFile.
ExcelFile ef2 = new ExcelFile();
 
// Imports all the tables from DataSet to new file.
foreach (DataTable dataTable in dataSet.Tables)
{
    // Add new worksheet to the file.
    ExcelWorksheet ws = ef2.Worksheets.Add(dataTable.TableName);
 
    // Change the value of the first cell in the DataTable.
    dataTable.Rows[0][0] = "This is new file!";
 
    // Insert the data from DataTable to the worksheet starting at cell "A1".
    ws.InsertDataTable(dataTable, "A1", true);
}
 
// Save the file to XLS format.
ef2.SaveXls("DataSet.xls");

QuestionCan I ge c# code?memberSelvakumar - B4 Sep '09 - 21:55 
Can I ge c# code?
GeneralMy vote of 1membervannavada3 Feb '09 - 7:48 
Runtime Exceptions
GeneralGetting error while adding WorksheetmemberMark_20006 Jun '08 - 3:36 
When i try to add worksheet in the existing workbook created,I get exception
Exception from HRESULT: 0x80020005
 
Anu workarround for this.
QuestionGot object reference Errormemberprakashkpd3 Oct '07 - 21:22 
hi,
i got the error "Object reference not set to an instance of an object." on the line "objExcel = New Excel.Application" while Creating a new object of the Excel application object. i added the dll Excel 11.0 but i cudn't get the namespace
"Imports Microsoft.Office.Interop" instead i get "Imports Microsoft.Office.Core"
im using windows xp.
 
solution pls...
 
regards
prakash
QuestionExporting Excel pivot charts in ASP.NET 1.1 Aplication?memberk_bhawna1 Jun '07 - 19:01 
It was a very good article to go through,
further could you please guide me in exporting pivot charts drawn in excel to an ASP.net application as there is a need to automate the pivot charts functionality of MS Excel.
 
Your guidence will be really appriciated...
 
Thanks
 

 
"IMPOSSIBLE itself says I M POSSIBLE... Smile | :) "
GeneralAvoiding the need for Excel on the servermemberpurplepangolin23 May '07 - 23:55 
If you save the file using one of the XML formats then you can construnct a valid Excel document without requiring Excel on the server. I do this from Oracle using string manipulation to construct the file and attach it to an email without needing to persist the file in a directory.
 
Obviously, there are any number of ways to construct the file depending on what tools you are familiar with. Using XSLT on a document produced using the DataSet.WriteXML method would be one possible approach.
GeneralRe: Avoiding the need for Excel on the servermemberjain.ashish2124 May '07 - 0:20 
Can u be more explanatory on what do you mean by saving the file using one of the XML formats so that I can construct a Excel document without using Excel on the server. Do you have any sample code or URL for the same.
 
If my mind can conceive it, and my heart can believe it, I know I can achieve it.

GeneralRe: Avoiding the need for Excel on the servermemberpurplepangolin24 May '07 - 0:39 
In Excel 2003 you can do File>Save As and then choose XML spreadsheet as one of the options. Documentation on the format is available here. Looking at the documentation, you can see that this functionality was introduced in Office 2000 and the schemas have subsequently been extended to allow additional features so you should be able to get this to work for versions of Excel from 2000 onwards depending on what features you use.
 
When I do this using the oracle email package I still set the extension to .xls rather than .xml and it works OK. I also set the MIME type to application/Excel.
 
My sample code is a bit messy and very specific to the type of file I'm constructing so probably not much use. I was constrained by the fact that I had a maximum of 32767 chars to play with and the format can be quite verbose unless you strip out a lot of the formatting info. I started off by creating the file manually in Excel and saving as an XML spreadsheet. I then opened up the file in a text editor and worked out which parts were boiler plate and which would need to be built up dynamically. Using this approach in conjunction with the docs it is pretty easy to use.
QuestionRe: Avoiding the need for Excel on the servermemberjain.ashish2124 May '07 - 22:16 
I am trying to build a SpreadSheetML file through XMLTextWriter. I am successfully able to create a Excel file without the use of Excel object. But while formatting I am not able to set the AutoFitWidth to 1. I am trying something as:
 
<Worksheet ss:Name="Sheet2">
   <ss:Table>
      <ss:Column ss:AutoFitWidth="1" />
      <ss:Row>
         <ss:Cell>
            <ss:Data ss:Type="String">Some text goes here.</ss:Data>
         </ss:Cell>
      </ss:Row>
   </ss:Table>
</Worksheet>
 
But still the column width of the first column in second sheet is the same.
 

 
If my mind can conceive it, and my heart can believe it, I know I can achieve it.
AnswerRe: Avoiding the need for Excel on the servermemberpurplepangolin24 May '07 - 22:39 
From the documentation:
"Specifies whether a column is automatically resized to fit numeric and date values. Columns are not resized to fit text data."
QuestionRe: Avoiding the need for Excel on the servermemberjain.ashish2124 May '07 - 22:44 
So does that means there no way we can automatically resize String data in SpreadsheetML..?
 
If my mind can conceive it, and my heart can believe it, I know I can achieve it.

AnswerRe: Avoiding the need for Excel on the servermemberpurplepangolin24 May '07 - 22:50 
If you set the WrapText attribute of the alignment element then it should allow the row height to change. This may ne acceptable for a block of text. You will have to set the column width to a fixed width though.
 
You could also write a function to check the length of your string and return a suitable value to use for the column width.
QuestionRe: Avoiding the need for Excel on the servermemberjain.ashish2124 May '07 - 23:06 
Is there a relational formula sort of thing for calculating the width of the cell based on the string data length? I mean as u were saying that we can write a function to return a suitable value to use for the column width?
 
If my mind can conceive it, and my heart can believe it, I know I can achieve it.

AnswerRe: Avoiding the need for Excel on the servermemberpurplepangolin24 May '07 - 23:11 
I don't know one off hand. It will be related to the font size as well as the length though. Also, if you are not using a fixed width font then it will be dependent on the actual content of the string. I expect that there will be something in the .Net framework though, there certainly used to be something in the Win32 API.
GeneralRe: Avoiding the need for Excel on the servermemberramki_mars22 Jul '08 - 21:18 
Hi,
 
I had exported to excel from the datatable(gridview data) using spreadsheel ML concept.
It worked fine and its fast.
 
But the resulted exported excel file size is much larger. May be because of the row, cell tags for every data.
 
Excel behavior:
For example, if I have xls file of size 17 MB. And save it as spreadsheel ML format
its size increased to 70 MB.
 

Thanks,
Ramki
AnswerRe: Avoiding the need for Excel on the servermemberminga4823 Sep '08 - 3:18 
<Table ss:ExpandedColumnCount="3" ss:ExpandedRowCount="19" x:FullColumns="1"
x:FullRows="1">
<Column ss:AutoFitWidth="0" ss:Width="43.5"/>
<Column ss:AutoFitWidth="0" ss:Width="430.5"/>
<Column ss:AutoFitWidth="0" ss:Width="165.75"/>
AnswerRe: Avoiding the need for Excel on the servermemberJrgen Andersson30 May '07 - 0:38 
You don't even need to use xml. Exceldocuments can be built directly with OLEDB.
Of course you'll lack all formatting that you can create if you use Excel
 
I'm using this module for creating excel files:
 

            Public Sub ExportDatasetToExcel(ByVal Location As String, ByVal DS As DataSet)
 
                  If My.Computer.FileSystem.FileExists(Location) Then
                        Try
                              My.Computer.FileSystem.DeleteFile(Location)
                        Catch ex As System.IO.IOException
                              Dim MsgStr As String = "The file is in use by another process. Close all programs that might use the file and try again."
                              MsgBox(MsgStr, MsgBoxStyle.Exclamation)
                              Exit Sub
                        Catch ex As Exception
                              Dim MsgStr As String = String.Format("This shouldn't happen, call support{0}Error message:{0}{1}{0}{0}Stacktrace:{0}{2}", vbCrLf, ex.Message, ex.StackTrace)
                              MsgBox(MsgStr, MsgBoxStyle.Exclamation)
                              Exit Sub
                        End Try
                  End If
                  Dim CreateString As String = ""
                  Dim Columns As String = ""
                  Dim Mark As String = ""
                  Dim ConnectionStr As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Location & ";Extended Properties=""Excel 8.0;HDR=YES"""
                  Using Connection As New OleDb.OleDbConnection(ConnectionStr)
                        Connection.Open()
                        For Each table As DataTable In DS.Tables
                              CreateString = "CREATE TABLE [" & table.TableName & "] ("
                              Columns = "("
                              Mark = "("
                              For Each Column As DataColumn In table.Columns
                                    CreateString &= OS(Column.ColumnName)
                                    Select Case Column.DataType.Name
                                          Case "SByte", "Byte", "Int16", "Int32", "Int64", "Decimal", "Double", "Single"
                                                CreateString &= " Number, "
                                          Case "Boolean"
                                                CreateString &= " Bit, "
                                          Case "Char", "String"
                                                CreateString &= " Memo, "
                                          Case "DateTime"
                                                CreateString &= " DateTime, "
                                          Case Else
                                                CreateString &= " Text, "
                                    End Select
                                    Columns &= OS(Column.ColumnName) & ", "
                                    Mark &= "?,"
                              Next
                              CreateString = CreateString.Remove(CreateString.Length - 2, 2)
                              CreateString &= ")"
                              Columns = Columns.Remove(Columns.Length - 2, 2)
                              Columns &= ")"
                              Mark = Mark.Remove(Mark.Length - 1, 1)
                              Mark &= ")"
                              Using Command As New OleDb.OleDbCommand(CreateString.ToString, Connection)
                                    Command.ExecuteNonQuery()
                              End Using
                              Using Adapter As New OleDb.OleDbDataAdapter("SELECT * FROM [" & table.TableName & "$]", Connection)
                                    Using ExcelDataset As New DataSet
                                          Debug.WriteLine("ExcelDataset.Locale.Name = " & ExcelDataset.Locale.Name)
                                          Adapter.Fill(ExcelDataset, table.TableName)
                                          Adapter.InsertCommand = New OleDb.OleDbCommand("INSERT INTO [" & table.TableName & "] " & Columns.ToString & " VALUES " & Mark.ToString, Connection)
                                          For Each Column As DataColumn In table.Columns
                                                Select Case Column.DataType.Name
                                                      Case "SByte", "Byte", "Int16", "Int32", "Int64", "Double", "Single" ', "Decimal"
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.Numeric, 100, OS(Column.ColumnName))
                                                      Case "Decimal"
                                                            'BIG, BIG Warning about this one, Inserting a a double intead of a Decimal is done to fix what appears to be a bug in OleDB
                                                            'when using another Language setting than en-US on the computer
                                                            '(changing Cultureinfo on the CurrentThread doesn't help, it might be OleDB starting a new thread)
                                                            'All suggestions are welcome
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.Double, 100, OS(Column.ColumnName))
 
                                                            'Here's my original that only works with en-US if anyone is interested
                                                            'Dim myParameter As New OleDb.OleDbParameter("@" & OS(Column.ColumnName), OleDb.OleDbType.Decimal, 100, OS(Column.ColumnName))
                                                            'myParameter.Precision = 8
                                                            'myParameter.Scale = 4
                                                            'Adapter.InsertCommand.Parameters.Add(myParameter)
 
                                                      Case "Boolean"
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.Boolean, 100, OS(Column.ColumnName))
                                                      Case "Char", "String"
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.Char, 65536, OS(Column.ColumnName))
                                                      Case "DateTime"
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.DBTimeStamp, 100, OS(Column.ColumnName))
                                                      Case Else
                                                            Adapter.InsertCommand.Parameters.Add("@" & OS(Column.ColumnName), OleDb.OleDbType.Char, 65536, OS(Column.ColumnName))
                                                End Select
                                          Next
                                          For Each Row As DataRow In table.Rows
                                                If Row.RowState <> DataRowState.Deleted Then
                                                      Dim ExcelRow As DataRow = ExcelDataset.Tables(table.TableName).NewRow
                                                      For i As Integer = 0 To table.Columns.Count - 1
                                                            ExcelRow.Item(i) = Row.Item(i)
                                                      Next
                                                      ExcelDataset.Tables(table.TableName).Rows.Add(ExcelRow)
                                                End If
                                          Next
                                          Adapter.Update(ExcelDataset, table.TableName)
                                    End Using
                              End Using
                        Next
                  End Using
                  System.GC.Collect()
            End Sub
 
            Private Function OS(ByVal Word As String) As String
                  Dim i As Integer = Word.IndexOf(".")
                  While i > -1
                        Word = Word.Remove(i, 1)
                        i = Word.IndexOf(".")
                  End While
                  Return Word
            End Function
 
This code is enhanced from an article that can be found at
http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=5129&lngWId=10
the original could only handle tables amongst other stuff
/Jörgen
GeneralRe: Avoiding the need for Excel on the servermemberramki_mars22 Jul '08 - 21:21 
Yes it can be done with this.
 
For relatively small data this should not be a probelm.
But if you deal with large file sizes, the approach using spreadsheet ML is very fast.
But one problem with this approach is reluted fiel size would be very large than the file got with OLEDB approach or Normal Excel Automation approach.
 

Thanks,
Ramki

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 24 May 2007
Article Copyright 2007 by jain.ashish21
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid