Exporting SQL Server data to Excel used to mean Office Interop — automating Excel itself from C#. That approach required Office installed on the machine, leaked COM objects if you missed a single ReleaseComObject, and Microsoft has long advised against it on servers, where it is unsupported and unstable. The modern way is to write the .xlsx file directly with a library like ClosedXML: no Excel installation, no COM, safe on servers and in containers, and faster.

This tutorial reads rows from SQL Server and produces a formatted Excel file in about forty lines.
Why not Office Interop anymore?
- Server-side automation of Office is unsupported — Microsoft's own guidance (KB 257757) says not to run Office in services, ASP.NET, or any unattended process. It deadlocks and leaks under load.
- It requires Excel installed on every machine that runs your code — a licensing and deployment burden, and a non-starter in Docker/Linux.
- COM interop is easy to get wrong — one missed release keeps
EXCEL.EXEalive in Task Manager forever.
ClosedXML writes the Open XML .xlsx format directly, so none of that applies. (EPPlus is a fine alternative but is commercially licensed for business use; ClosedXML is MIT.)
Set up the project
dotnet new console -n SqlToExcel
cd SqlToExcel
dotnet add package ClosedXML
dotnet add package Microsoft.Data.SqlClient
Microsoft.Data.SqlClient is the current SQL Server ADO.NET provider — the old System.Data.SqlClient is in maintenance mode.
Read from SQL Server and write the workbook
using ClosedXML.Excel;
using Microsoft.Data.SqlClient;
const string connectionString = "<your connection string>";
using var connection = new SqlConnection(connectionString);
using var command = new SqlCommand(
"SELECT ProductID, Name, ProductNumber, Color, ListPrice " +
"FROM Production.Product ORDER BY ProductID",
connection);
connection.Open();
using var reader = command.ExecuteReader();
using var workbook = new XLWorkbook();
var sheet = workbook.Worksheets.Add("Products");
// header row straight from the result set's columns
for (var col = 0; col < reader.FieldCount; col++)
sheet.Cell(1, col + 1).Value = reader.GetName(col);
sheet.Row(1).Style.Font.Bold = true;
// data rows
var row = 2;
while (reader.Read())
{
for (var col = 0; col < reader.FieldCount; col++)
sheet.Cell(row, col + 1).Value = reader.IsDBNull(col) ? "" : reader.GetValue(col).ToString();
row++;
}
sheet.Column(5).Style.NumberFormat.Format = "#,##0.00";
sheet.Columns().AdjustToContents();
workbook.SaveAs("Products.xlsx");
Console.WriteLine($"Exported {row - 2} products to Products.xlsx");
Running it against the AdventureWorks Production.Product table:
Exported 10 products to Products.xlsx
Open the file and you get a bold header row, formatted prices, and auto-fitted columns. Because the header loop reads reader.GetName(col), the same code exports any query — change the SQL and the spreadsheet follows.
The AdventureWorks sample database used here is available in the SQLSampleDatabase scripts.
Even shorter: insert a DataTable as a table
If you already have a DataTable, ClosedXML inserts it in one call — headers, data, and an Excel table with filter dropdowns included:
var table = new DataTable();
using (var adapter = new SqlDataAdapter(sql, connectionString))
adapter.Fill(table);
using var workbook = new XLWorkbook();
workbook.Worksheets.Add(table, "Products");
workbook.SaveAs("Products.xlsx");
Returning the file from a Web API
The same workbook can be streamed from an ASP.NET Core endpoint instead of saved to disk:
[HttpGet("export")]
public IActionResult Export()
{
using var workbook = BuildWorkbook(); // the code above, returning XLWorkbook
var stream = new MemoryStream();
workbook.SaveAs(stream);
stream.Position = 0;
return File(stream,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Products.xlsx");
}
The browser downloads Products.xlsx like any other file — more on returning files in
returning a file using FileResult.
Wrap-up
- Never automate Excel via Interop on a server — it's unsupported and fragile.
- ClosedXML (MIT-licensed) writes real
.xlsxfiles with styling, formulas, and tables, and runs anywhere .NET runs — Windows, Linux, containers. - The reader-loop pattern exports any query; the
DataTableoverload is a one-liner; aMemoryStreamturns it into an API download.
Frequently asked questions
Why not use Microsoft Office Interop to export to Excel?
Interop needs Excel installed, leaks COM objects easily, and Microsoft does not support automating Office on servers or in services. Writing the .xlsx directly avoids all of that.
Is ClosedXML free?
Yes. ClosedXML is open source under the MIT licence. EPPlus is an alternative but needs a commercial licence for business use.
How do I export a DataTable to Excel in C#?
With ClosedXML, create an XLWorkbook, call worksheet.Cell(1, 1).InsertTable(dataTable), and save the workbook to a file or a stream.
How do I return the Excel file from an ASP.NET Core API?
Save the workbook to a MemoryStream and return File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "export.xlsx").
Comments (0)
No comments yet — be the first to share your thoughts.