All articles

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

Export SQL Server query results to Excel with ClosedXML — no Office installation, no COM Interop, safe on servers and containers, with styling and a Web API download endpoint.

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.
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.