How can I export a DataTable
to Excel in C#? I am using Windows Forms. The DataTable
is associated with a DataGridView
control. I have
I wanted to add this answer because I spent a great deal of time looking for a fast, reliable method to do this and no complete examples of using OpenXMLWriter for this purpose existed anywhere that I could find.
First, COM/Interop (which many of the other answers use) is OK for this purpose, but it suffers from some sensitivities. I've used it for decades and it's mostly stable, but when implementing a data warehouse front-end for hundreds of users, I found it to be subject to too many issues depending on the machine and what the user did, so I switched to OpenXML. OpenXML DOM is fairly good for this purpose, but it's slower than using OpenXMLWriter. When you get into large datasets (100K+) with lots of columns, DOM is much slower than OpenXMLWriter, so I use the latter. The method below writes 420K+ rows with 30+ fields in less than 30 seconds.
I hope the comments are sufficient to guide anyone through what it's doing. It is simplified, in that it writes all values to the file as strings, but you can implement logic to write various datatypes (and use various cell formats) based on the content of your data. You can also adapt this for use on a DataGridView (instead of a DataTable) by changing just a few things (namely the loops through columns/rows).
A reference to DocumentFormat.OpenXML (d/l with the OpenXML SDK) and WindowsBase is required.
Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Spreadsheet
Imports DocumentFormat.OpenXml.Packaging
Public Sub ExportToExcelXML(ByRef dt As DataTable, filename As String)
Dim wbp As WorkbookPart, wsp As WorksheetPart
'If this DataTable has more rows in it than can fit in Excel, throw an exception
If dt.Rows.Count > 1048575 Then Throw New Exception("The DataTable is too large to export to Excel.")
'Delete any previous file of the same name that may exist.
File.Delete(filename)
'Create an OpenXML SpreadsheetDocument...
Using xls = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook)
'Add a WorkbookPart to the Spreadsheet Doc, then add a WorksheetPart to the WorkbookPart.
wbp = xls.AddWorkbookPart()
wsp = wbp.AddNewPart(Of WorksheetPart)
'Now we need to add the "StyleSheet" to the WorkbookPart (that we just added above). This will allow us to apply formatting to our Cells.
'Add the WbStylesPart and the StyleSheet.
Dim stp As WorkbookStylesPart = wbp.AddNewPart(Of WorkbookStylesPart)
Dim ss As New Stylesheet
'Create the only two Fonts we're going to use (Regular and Bold).
Dim fBold As New Font
fBold.Append(New Bold)
Dim fnts As New Fonts
fnts.Append(New Font) 'This creates the default (unmodified, regular) Font. It's added first, so its index is 0.
fnts.Append(fBold) 'This creates the Bold font. It's added second, so its index is 1.
'Create the default Fill/Border settings (these have to be here, even though I don't set any custom fills/borders).
Dim flls As New Fills
Dim brdrs As New Borders
flls.Append(New Fill)
brdrs.Append(New Border)
'Now I have to add formats (NumberFormat and CellFormat). First, you create a NumberFormat. This is basically the pattern of
' the format (i.e. "@" for Text). For now, I only need a Text format, but I can add more patterns if needed.
' I give the format an ID of 164, since 163 is where the built-in Excel formats end.
Dim nbrfmts As New NumberingFormats
nbrfmts.Append(New NumberingFormat With {.NumberFormatId = 164, .FormatCode = "@"})
'Create the first two CellFormats: Default, which will have an index of 0 and "Header" (Bold/Centered) with an index of 1.
Dim cellfmts As New CellFormats()
cellfmts.Append(New CellFormat() With {.FontId = 0, .NumberFormatId = 164, .FillId = 0, .BorderId = 0})
cellfmts.Append(New CellFormat() With {.FontId = 1, .NumberFormatId = 164,
.Alignment = New Alignment() With {.WrapText = True, .Horizontal = HorizontalAlignmentValues.Center}})
'Add all of the Fonts/Fills/Borders/etc to the StyleSheet and add it all to the WorkbookStylesPart.
ss.Append(fnts)
ss.Append(flls)
ss.Append(brdrs)
ss.Append(cellfmts)
ss.NumberingFormats = nbrfmts
stp.Stylesheet = ss
stp.Stylesheet.Save()
'Now create an OpenXMLWriter using the WorksheetPart to write the cells to the worksheet.
Using oxw As OpenXmlWriter = OpenXmlWriter.Create(wsp)
'Write the start element for the Worksheet and the Columns...
oxw.WriteStartElement(New Worksheet)
oxw.WriteStartElement(New Columns())
'Now I'm going to loop through the columns in the DataTable...
For c As Integer = 0 To dt.Columns.Count - 1
'Now we'll get the width for the column. To do this, we loop through all of the rows and measure the width of the text
' using the default Excel Font (currently Font: Calibri Size: 11) and return the largest width (in pixels) to use below.
' Why not do this loop below (when I loop through the rows to write the Cells)? Because you can't. You have to
' write the Column XML first before writing the SheetData/Row/Cell XML (I confirmed this by trying it), so there's
' no way (that I'm aware of) to avoid looping through all of the rows twice if you want to AutoFit.
'Setup vars we'll use for getting the column widths (below).
Dim g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero)
Dim fnt = New System.Drawing.Font("Calibri", 11)
Dim wid As Double = 0
'Get the width of the header (because if this is wider than the widest value, we'll use the header text's width).
' I found that adding 2 pixels to the width was necessary to get the column as wide as Excel would make it.
Dim tmp As Double = g.MeasureString(dt.Columns(c).ColumnName, New System.Drawing.Font(fnt, System.Drawing.FontStyle.Bold)).Width + 2
'Loop through the rows in the dt and get the width of the value in that row/col. If it's wider than the widest
' width we've encountered thus far, use the new wider width as our basis.
For Each row As DataRow In dt.Rows
If tmp > wid Then wid = tmp
tmp = g.MeasureString(row(c).ToString, fnt).Width
Next
'Set the column attributes and write it to the file. The Width is set using a formula that converts from pixels to Excel's column width values.
Dim oxa As New List(Of OpenXmlAttribute) From {New OpenXmlAttribute("min", Nothing, c + 1), New OpenXmlAttribute("max", Nothing, c + 1),
New OpenXmlAttribute("width", Nothing, System.Math.Round((wid - 12 + 5) / 7D + 1, 2))}
oxw.WriteStartElement(New Column(), oxa)
oxw.WriteEndElement()
Next
'CLose out the Columns collection.
oxw.WriteEndElement()
'Write the start element for the SheetData...
oxw.WriteStartElement(New SheetData)
'Write the start element for the Header row.
oxw.WriteStartElement(New Row)
'Loop through the Columns in the dt.
For Each col As DataColumn In dt.Columns
'Write a cell for this column's Header. All Header cells are written with a DataType of String ("str").
' I ALSO apply the "Header" CellFormat (StyleIndex 1) to all of the Header Cells. This makes them Bold and Centered.
WriteCell(oxw, col.ColumnName, "str", 1)
Next
'Close out the Header row.
oxw.WriteEndElement()
'Loop through all of the rows in the dt...
For Each row As DataRow In dt.Rows
'Write a StartElement for this row...
oxw.WriteStartElement(New Row)
'Loop through all of the columns in the dt...
For c As Integer = 0 To dt.Columns.Count - 1
'Write a value in this row/column to the Excel file. I use the datatype of "String" and the default CellFormat/StyleIndex.
WriteCell(oxw, row(c).ToString, "str", 0)
Next
'Close out this row.
oxw.WriteEndElement()
Next
'Close out the Worksheet and SheetData elements...
oxw.WriteEndElement()
oxw.WriteEndElement()
End Using
'Now we're going to create an OpenXMLWriter using the WorkbookPart (that we created above)...
Using oxw As OpenXmlWriter = OpenXmlWriter.Create(wbp)
'Add starting elements for the Workbook and Sheets collection.
oxw.WriteStartElement(New Workbook())
oxw.WriteStartElement(New Sheets())
'Add the Sheet (name the Sheet after the file name minus the extension).
oxw.WriteElement(New Sheet() With {.Name = Path.GetFileNameWithoutExtension(filename), .SheetId = 1, .Id = xls.WorkbookPart.GetIdOfPart(wsp)})
'Write End elements for the Workbook/Sheets
oxw.WriteEndElement()
oxw.WriteEndElement()
End Using
End Using
End Sub
'This Sub is used to write a value to a Cell using OpenXMLWriter.
Private Sub WriteCell(ByRef oxw As OpenXmlWriter, value As String, datatype As String, style As UInt32Value)
Dim oxa As New List(Of OpenXmlAttribute) From {New OpenXmlAttribute("t", Nothing, datatype), New OpenXmlAttribute("s", Nothing, style)}
oxw.WriteStartElement(New Cell(), oxa)
If value <> Nothing Then oxw.WriteElement(New CellValue(value))
oxw.WriteEndElement()
End Sub