- Download source code (VB.NET and C#) — 23.4 KB
Introduction
Exporting data from a .NET application to Excel is a very common requirement. A simple search on the Web results in several examples that show us the method to copy data and put it into the Excel cells. However, there is a payload with this method: each interaction to put a value into an Excel cell requires an InterOp invocation. If the amount of data to transfer is huge, we have a problem with the performance of the method. Is there a better way to accomplish this?
Traditional «COPY CELL-BY-CELL» Method
Searching the Web for a method to transfer data to Excel, the most commonly used method consists of copying the values cell by cell into Excel. The following C# code shows how to transfer data from a DataTable
to an Excel sheet, copying each value cell by cell:
for (int col = 0; col < dataTable.Columns.Count; col++) { for (int row = 0; row < dataTable.Rows.Count; row++) { excelSheet.Cells[row + 1, col + 1] = dataTable.Rows[row].ItemArray[col]; } }
Each InterOp invocation has an associated payload in performance, so a large amount of data can degenerate our application.
A «Fast Bulk-Copy» Method
Our method consists of using the Value2
property for the Range
class provided by the Microsoft Excel Object Library. We can select a range of cells, and assign a value for all of them, with just one InterOp invocation. To correctly assign a value to a range of cells, we can use a bi-dimensional object array. The following C# code shows how to transfer data from a bi-dimensional object array to a range of cells:
excelSheet.get_Range("A1:H25", Type.Missing).Value2 = bidimensionalObjectArray;
Measuring the Performance
The source code included with this article shows a small Windows application which uses the two described methods to export the same data to Excel. It shows the time that it takes for each method to finish. This DEMO uses the Northwind database to create an SQL Server local connection. It generates a DataSet
with the content of the Customers
table. To make the amount of data more significant, we duplicate the DataTable
to obtain 24 copies from it. Then we apply the two methods to generate two Excel books, one for each method.
The source code includes a C# and a VB.NET version for the DEMO application. My own testing shows me that this method is about 35 times faster. Test it and arrive at your own conclusions.
History
- November 28, 2007: First publication
This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.
I have a table with around 2500 rows and 70 columns of simple data (yes/no). I would like to export this data to Excel file.
175000 cells doesn’t look like really a lot, so I would expect it to export the data in up to 10 seconds.
What is the way to implement it?
P.S. I have tried EPPlus but it seems to be to slow (I accessed the cells one by one).
P. P. S. I would probably also like to use some styling (like to set bold text etc.). But it seems it’s actually what slows done EPPlus exporting.
asked Aug 26, 2016 at 22:49
wituawitua
371 silver badge6 bronze badges
3
The way to go is OpenXML library. This is super fast and can do 175K cells in 10 seconds. You can also do custom formatting with this. It does not require excel to be installed either.
- Install Open XML SDK
- You can install the Open XML productivity tool and open an existing excel, this will generate C# code to export such an excel. This is useful to understand how the library works.
Look here for examples on using this open source Microsoft library.
I would suggest you use the SAX approach for maximum performance.
answered Aug 26, 2016 at 22:57
Export data to Excel in ASP.NET C#
It is not so rare that you have to provide some sort of export of your data stored in database. I have a feeling that clients really like to have that functionality, probably because they are more comfortable with Excel than with any other tool.
First thing that might pop to your mind is to add a reference to Office library, but that would require that you have Office on the hosting machine which might not be the case as most of hosts do not have it.
Second thing is that if you add reference to Office library you would probably end up with using of unmanaged code which, sometimes is not so easy to handle regarding releasing the memory and processes that support it.
Luckily you do not need to have Office installed on your hosting machine to produce an Excel sheet. So far, I’ve been doing this two ways:
Exporting data to CSV (Comma Separated Values) file
CSV is legacy, but still often used
DataTable result = new DataTable(); string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["AdventureWorks2008R2"].ConnectionString; DateTime startDate = SqlDateTime.MinValue.Value; DateTime endDate = SqlDateTime.MaxValue.Value; if (!DateTime.TryParse(this.dpStartDate.Text, out startDate)) { startDate = SqlDateTime.MinValue.Value; } if (!DateTime.TryParse(this.dpEndDate.Text, out endDate)) { endDate = SqlDateTime.MaxValue.Value; } using (SqlCommand command = new SqlCommand()) { using (SqlConnection connection = new SqlConnection(connectionString)) { command.CommandText = "SELECT * FROM Employee WHERE HireDate BETWEEN @StartDate AND @EndDate"; command.Connection = connection; command.CommandType = CommandType.Text; command.Parameters.AddWithValue("@StartDate", startDate); command.Parameters.AddWithValue("@EndDate", endDate); using (SqlDataAdapter adapter = new SqlDataAdapter(command)) { adapter.Fill(result); if (result != null) { using (result) { Response.ContentEncoding = new UTF8Encoding(true); Response.Charset = Encoding.UTF8.WebName; Response.ContentType = "application/vnd.ms-excel; charset=utf-8"; Response.AddHeader("content-disposition", string.Format("attachment;filename=dataexport_{0}.csv", DateTime.Now.ToString("yyyyMMddHHmmss"))); StringBuilder fileContent = new StringBuilder(); foreach (var col in result.Columns) { fileContent.Append(col.ToString() ","); } fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1); foreach (DataRow dr in result.Rows) { foreach (var column in dr.ItemArray) { fileContent.Append(""" column.ToString() "","); } fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1); } //Write encoding characters first Response.Write('uFEFF'); //Write the content Response.Write(fileContent.ToString()); Response.End(); } } } } }
at for exporting tabular data. Excel is pretty good in dealing with CSV data, so all you have to do in your ASP.NET application is to return CSV data with headers that will offer user to open file using Excel application.
For example I’m going to export filtered employee records from MS sample AdventureWorks database which can be found on Microsoft’s website http://msftdbprodsamples.codeplex.com/releases/view/93587
Note
For the complicity of code, I used simple ADO parametarized query for fetching data
DataTable result = new DataTable(); string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["AdventureWorks2008R2"].ConnectionString; DateTime startDate = SqlDateTime.MinValue.Value; DateTime endDate = SqlDateTime.MaxValue.Value; if (!DateTime.TryParse(this.dpStartDate.Text, out startDate)) { startDate = SqlDateTime.MinValue.Value; } if (!DateTime.TryParse(this.dpEndDate.Text, out endDate)) { endDate = SqlDateTime.MaxValue.Value; } using (SqlCommand command = new SqlCommand()) { using (SqlConnection connection = new SqlConnection(connectionString)) { command.CommandText = "SELECT * FROM Employee WHERE HireDate BETWEEN @StartDate AND @EndDate"; command.Connection = connection; command.CommandType = CommandType.Text; command.Parameters.AddWithValue("@StartDate", startDate); command.Parameters.AddWithValue("@EndDate", endDate); using (SqlDataAdapter adapter = new SqlDataAdapter(command)) { adapter.Fill(result); if (result != null) { using (result) { Response.ContentEncoding = new UTF8Encoding(true); Response.Charset = Encoding.UTF8.WebName; Response.ContentType = "application/vnd.ms-excel; charset=utf-8"; Response.AddHeader("content-disposition", string.Format("attachment;filename=dataexport_{1}.csv", DateTime.Now.ToString("yyyyMMddHHmmss"))); StringBuilder fileContent = new StringBuilder(); foreach (var col in result.Columns) { fileContent.Append(col.ToString() + ","); } fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1); foreach (DataRow dr in result.Rows) { foreach (var column in dr.ItemArray) { fileContent.Append(""" + column.ToString() + "","); } fileContent.Replace(",", System.Environment.NewLine, fileContent.Length - 1, 1); } //Write encoding characters first Response.Write('uFEFF'); //Write the content Response.Write(fileContent.ToString()); Response.End(); } } } } }
Exporting data as HTML table
This is another way to push non native Excel file to Excel and make it displayed as tabular data. The idea is to load DataTable or any collection data source and bind it to a GridView.
When it’s binded, render out control layout which will be an HTML table and push it to excel by attaching headers, same as in previous example.
DataTable result = new DataTable(); string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["AdventureWorks2008R2"].ConnectionString; DateTime startDate = SqlDateTime.MinValue.Value; DateTime endDate = SqlDateTime.MaxValue.Value; if (!DateTime.TryParse(this.dpStartDate.Text, out startDate)) { startDate = SqlDateTime.MinValue.Value; } if (!DateTime.TryParse(this.dpEndDate.Text, out endDate)) { endDate = SqlDateTime.MaxValue.Value; } using (SqlCommand command = new SqlCommand()) { using (SqlConnection connection = new SqlConnection(connectionString)) { command.CommandText = "SELECT * FROM Employee WHERE HireDate BETWEEN @StartDate AND @EndDate"; command.Connection = connection; command.CommandType = CommandType.Text; command.Parameters.AddWithValue("@StartDate", startDate); command.Parameters.AddWithValue("@EndDate", endDate); using (SqlDataAdapter adapter = new SqlDataAdapter(command)) { adapter.Fill(result); if (result != null) { using (result) { Response.ContentEncoding = new UTF8Encoding(true); Response.Charset = Encoding.UTF8.WebName; Response.ContentType = "application/vnd.ms-excel; charset=utf-8"; Response.AddHeader("content-disposition", string.Format("attachment;filename=dataexport_{0}.xls", DateTime.Now.ToString("yyyyMMddHHmmss"))); StringWriter sw = new StringWriter(); HtmlTextWriter hw = new HtmlTextWriter(sw); var grid = new GridView(); grid.DataSource = result; grid.DataBind(); grid.RenderControl(hw); //Write encoding characters first Response.Write('uFEFF'); //Write the content Response.Write(sw); Response.End(); } } } } }
Note
GridView control does not need to have DataTable or DataSource as a source to bind with. You can bind it to pretty much any kind of collection.
References
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
About the author
DEJAN STOJANOVIC
Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects
CONNECT WITH DEJAN
I have an interesting conundrum here, how do I quickly (under 1 minute) export a large datatable (filled from SQL, 35,000 rows) into an Excel spreadsheet for users. I have code in place that can handle the export, and while nothing is «wrong» with the code per se, it is infuriatingly slow taking 4 minutes to export the entire file (sometimes longer if a user has less RAM or is running more on their system). Sadly, this is an improvement over the 10+ minutes it used to take using our old method. Simply put, can this be made any faster, without using 3rd party components? If so, how? My code is as follows, the slow down occurs between messageboxes 6 and 7 where each row is written. Thank you all for taking the time to take a look at this:
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnJeffTest.Click
Test(MySPtoExport)
End Sub
Private Sub Test(ByVal SQL As String)
'Declare variables used to execute the VUE Export stored procedure
MsgBox("start stop watch")
Dim ConnectionString As New SqlConnection(CType(ConfigurationManager.AppSettings("ConnString"), String))
Dim cmdSP As New SqlClient.SqlCommand
Dim MyParam As New SqlClient.SqlParameter
Dim MyDataAdapter As New SqlClient.SqlDataAdapter
Dim ExportDataSet As New DataTable
Dim FilePath As String
MsgBox("stop 1 - end of declare")
Try
' open the connection
ConnectionString.Open()
' Use the connection for this sql command
cmdSP.Connection = ConnectionString
'set this command as a stored procedure command
cmdSP.CommandType = CommandType.StoredProcedure
'get the stored procedure name and plug it in
cmdSP.CommandText = SQL
'Add the Start Date parameter if required
Select Case StDt
Case Nothing
' there's no parameter to add
Case Is = 0
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add("@StartDate", SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtStartDate.Text
End Select
MsgBox("stop 2 - sql ready")
'Add the End Date parameter if required
Select Case EdDt
Case Nothing
' there's no parameter to add
Case Is = 0
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add("@EndDate", SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtEndDate.Text
End Select
'Add the single parameter 1 parameter if required
Select Case SPar1
Case Is = Nothing
' there's no parameter to add
Case Is = ""
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add(SPar1, SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtSingleReportCrt1.Text
End Select
'Add the single parameter 2 parameter if required
Select Case Spar2
Case Is = Nothing
' there's no parameter to add
Case Is = ""
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add(Spar2, SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtSingleReportCrt2.Text
End Select
MsgBox("stop 3 - params ready")
'Prepare the data adapter with the selected command
MyDataAdapter.SelectCommand = cmdSP
' Set the accept changes during fill to false for the NYPDA export
MyDataAdapter.AcceptChangesDuringFill = False
'Fill the Dataset tables (Table 0 = Exam Eligibilities, Table 1 = Candidates Demographics)
MyDataAdapter.Fill(ExportDataSet)
'Close the connection
ConnectionString.Close()
'refresh the destination path in case they changed it
SPDestination = txtPDFDestination.Text
MsgBox("stop 4 - procedure ran, datatable filled")
Select Case ExcelFile
Case True
FilePath = SPDestination & lblReportName.Text & ".xls"
Dim _excel As New Microsoft.Office.Interop.Excel.Application
Dim wBook As Microsoft.Office.Interop.Excel.Workbook
Dim wSheet As Microsoft.Office.Interop.Excel.Worksheet
wBook = _excel.Workbooks.Add()
wSheet = wBook.ActiveSheet()
Dim dt As System.Data.DataTable = ExportDataSet
Dim dc As System.Data.DataColumn
Dim dr As System.Data.DataRow
Dim colIndex As Integer = 0
Dim rowIndex As Integer = 0
MsgBox("stop 5 - excel stuff declared")
For Each dc In dt.Columns
colIndex = colIndex + 1
_excel.Cells(1, colIndex) = dc.ColumnName
Next
MsgBox("stop 6 - Header written")
For Each dr In dt.Rows
rowIndex = rowIndex + 1
colIndex = 0
For Each dc In dt.Columns
colIndex = colIndex + 1
_excel.Cells(rowIndex + 1, colIndex) = dr(dc.ColumnName)
Next
Next
MsgBox("stop 7 - rows written")
wSheet.Columns.AutoFit()
MsgBox("stop 8 - autofit complete")
Dim strFileName = SPDestination & lblReportName.Text & ".xls"
If System.IO.File.Exists(strFileName) Then
System.IO.File.Delete(strFileName)
End If
MsgBox("stop 9 - file checked")
wBook.SaveAs(strFileName)
wBook.Close()
_excel.Quit()
End Select
MsgBox("File " & lblReportName.Text & " Exported Successfully!")
'Dispose of unneeded objects
MyDataAdapter.Dispose()
ExportDataSet.Dispose()
StDt = Nothing
EdDt = Nothing
SPar1 = Nothing
Spar2 = Nothing
MyParam = Nothing
cmdSP.Dispose()
cmdSP = Nothing
MyDataAdapter = Nothing
ExportDataSet = Nothing
Catch ex As Exception
' Something went terribly wrong. Warn user.
MessageBox.Show("Error: " & ex.Message, "Stored Procedure Running Process ", _
MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
'close the connection in case is still open
If Not ConnectionString.State = ConnectionState. Then
ConnectionString.Close()
ConnectionString = Nothing
End If
' reset the fields
ResetFields()
End Try
End Sub
Fast Excel import/export for Laravel, thanks to Spout.
See benchmarks below.
Quick start
Install via composer:
composer require rap2hpoutre/fast-excel
Export a Model to .xlsx
file:
use Rap2hpoutreFastExcelFastExcel; use AppUser; // Load users $users = User::all(); // Export all users (new FastExcel($users))->export('file.xlsx');
Export
Export a Model or a Collection:
$list = collect([ [ 'id' => 1, 'name' => 'Jane' ], [ 'id' => 2, 'name' => 'John' ], ]); (new FastExcel($list))->export('file.xlsx');
Export xlsx
, ods
and csv
:
$invoices = AppInvoice::orderBy('created_at', 'DESC')->get(); (new FastExcel($invoices))->export('invoices.csv');
Export only some attributes specifying columns names:
(new FastExcel(User::all()))->export('users.csv', function ($user) { return [ 'Email' => $user->email, 'First Name' => $user->firstname, 'Last Name' => strtoupper($user->lastname), ]; });
Download (from a controller method):
return (new FastExcel(User::all()))->download('file.xlsx');
Import
import
returns a Collection:
$collection = (new FastExcel)->import('file.xlsx');
Import a csv
with specific delimiter, enclosure characters and «gbk» encoding:
$collection = (new FastExcel)->configureCsv(';', '#', 'gbk')->import('file.csv');
Import and insert to database:
$users = (new FastExcel)->import('file.xlsx', function ($line) { return User::create([ 'name' => $line['Name'], 'email' => $line['Email'] ]); });
Facades
You may use FastExcel with the optional Facade. Add the following line to config/app.php
under the aliases
key.
'FastExcel' => Rap2hpoutreFastExcelFacadesFastExcel::class,
Using the Facade, you will not have access to the constructor. You may set your export data using the data
method.
$list = collect([ [ 'id' => 1, 'name' => 'Jane' ], [ 'id' => 2, 'name' => 'John' ], ]); FastExcel::data($list)->export('file.xlsx');
Global helper
FastExcel provides a convenient global helper to quickly instantiate the FastExcel class anywhere in a Laravel application.
$collection = fastexcel()->import('file.xlsx'); fastexcel($collection)->export('file.xlsx');
Advanced usage
Export multiple sheets
Export multiple sheets by creating a SheetCollection
:
$sheets = new SheetCollection([ User::all(), Project::all() ]); (new FastExcel($sheets))->export('file.xlsx');
Use index to specify sheet name:
$sheets = new SheetCollection([ 'Users' => User::all(), 'Second sheet' => Project::all() ]);
Import multiple sheets
Import multiple sheets by using importSheets
:
$sheets = (new FastExcel)->importSheets('file.xlsx');
You can also import a specific sheet by its number:
$users = (new FastExcel)->sheet(3)->import('file.xlsx');
Import multiple sheets with sheets names:
$sheets = (new FastExcel)->withSheetsNames()->importSheets('file.xlsx');
Export large collections with chunk
Export rows one by one to avoid memory_limit
issues using yield
:
function usersGenerator() { foreach (User::cursor() as $user) { yield $user; } } // Export consumes only a few MB, even with 10M+ rows. (new FastExcel(usersGenerator()))->export('test.xlsx');
Add header and rows style
Add header and rows style with headerStyle
and rowsStyle
methods.
use OpenSpoutCommonEntityStyleStyle; $header_style = (new Style())->setFontBold(); $rows_style = (new Style()) ->setFontSize(15) ->setShouldWrapText() ->setBackgroundColor("EDEDED"); return (new FastExcel($list)) ->headerStyle($header_style) ->rowsStyle($rows_style) ->download('file.xlsx');
Why?
FastExcel is intended at being Laravel-flavoured Spout:
a simple, but elegant wrapper around Spout with the goal
of simplifying imports and exports. It could be considered as a faster (and memory friendly) alternative
to Laravel Excel, with less features.
Use it only for simple tasks.
Benchmarks
Tested on a MacBook Pro 2015 2,7 GHz Intel Core i5 16 Go 1867 MHz DDR3.
Testing a XLSX export for 10000 lines, 20 columns with random data, 10 iterations, 2018-04-05. Don’t trust benchmarks.
Average memory peak usage | Execution time | |
---|---|---|
Laravel Excel | 123.56 M | 11.56 s |
FastExcel | 2.09 M | 2.76 s |
Still, remember that Laravel Excel has many more features.
- Remove From My Forums
-
Question
-
User-1821287852 posted
Hi all,
I am developing an MVC ASP.Net application that needs to export large data from the database to excel.
By large I mean 12K records with 26 columns of different types, each.
Is there any simple and straightforward way of implementing this that doesn’t take several minutes (more than 5) to process and that still keeps my excel columns format correct?
I have been using EPPlus, but it is very slow.
Code here:
oGrid.DataSource = oTickets.ToList(); oGrid.DataBind(); ExcelPackage oExcel = new ExcelPackage(); var oWS = oExcel.Workbook.Worksheets.Add("Tickets"); var iTotalCols = oGrid.Rows[0].Cells.Count; var iTotalRows = oGrid.Rows.Count; var oHeaderRow = oGrid.HeaderRow; for (var i = 1; i <= iTotalCols; i++) { oWS.Cells[1, i].Value = oHeaderRow.Cells[i - 1].Text; } for (var j = 1; j <= iTotalRows; j++) { for (var i = 1; i <= iTotalCols; i++) { var oTicket = oTickets.ToList().ElementAt(j - 1); if (oTicket.GetType().GetProperty(oHeaderRow.Cells[i - 1].Text) != null) { oWS.Cells[j + 1, i].Value = oTicket.GetType().GetProperty(oHeaderRow.Cells[i - 1].Text).GetValue(oTicket, null); } } } using (var oMemoryStream = new MemoryStream()) { if (bSend) { oExcel.SaveAs(oMemoryStream); oMemoryStream.Position = 0; } else { Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; Response.AddHeader("content-disposition", "attachment; filename=" + sFileName); oExcel.SaveAs(oMemoryStream); oMemoryStream.WriteTo(Response.OutputStream); Response.Flush(); Response.End(); } }
Thanks,
Bruno
Answers
-
User-1821287852 posted
I got rid of the GetProperty, GetType, etc. functions and it becames fast enough.
Thanks.
-
Marked as answer by
Thursday, October 7, 2021 12:00 AM
-
Marked as answer by