All articles

Export SQL Data to Excel in C# Without Office Interop (ClosedXML)

Export SQL Server data to Excel in C# with ClosedXML instead of Office Interop: no Office install, safe on servers and containers, with a Web API download.

0 · log in to like, save & follow Share on LinkedIn Share on X

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.

cover

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.EXE alive 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 .xlsx files with styling, formulas, and tables, and runs anywhere .NET runs — Windows, Linux, containers.
  • The reader-loop pattern exports any query; the DataTable overload is a one-liner; a MemoryStream turns 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").

Related reading

Enjoyed this article? Get the best GeeksArray articles in your inbox — once a week, no spam, unsubscribe anytime.

Comments (0)

Log in to join the conversation.

No comments yet — be the first to share your thoughts.