Recursive AI

Stream a large XLSX file in C#

OneDocument is a free, open-source Word, Excel and PowerPoint document library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Read an XLSX worksheet one row at a time without loading the workbook model.

Terminal
dotnet add package \
  RecursiveAI.OneDocument.Spreadsheets

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using RecursiveAI.OneDocument.Spreadsheets;
using RecursiveAI.OneDocument.Spreadsheets
    .Streaming;
 
using var book = XlsxStreamReader.Open(
    "input.xlsx");
using var sheet = book.OpenSheet("Data");
double total = 0;
while (sheet.ReadRow())
    while (sheet.ReadCell())
        if (sheet.Kind == CellValueKind.Number)
            total += sheet.Number;
Console.WriteLine($"Total: {total}");
Text
Total: 263

input.xlsx has a Data sheet with these cells:

Text
Customer | Total | Id
Ada      | 42    | 101
Lin      | 18    | 102

Malformed XML raises OfficeFormatException; resource limits raise OfficeLimitException, with the part and byte offset.

Unzip the example, then dotnet run.

Variations

Sum only the second column

Exclude the numeric Id column: the two amounts sum to 60 rather than 263.

C#
using RecursiveAI.OneDocument.Spreadsheets;
using RecursiveAI.OneDocument.Spreadsheets
    .Streaming;
 
using var book = XlsxStreamReader.Open(
    "input.xlsx");
using var sheet = book.OpenSheet("Data");
double total = 0;
while (sheet.ReadRow())
    while (sheet.ReadCell())
        if (sheet.Column == 2 &&
            sheet.Kind == CellValueKind.Number)
            total += sheet.Number;
Console.WriteLine($"Total: {total}");
Text
Total: 60

The short answer

Use XlsxStreamReader and a SheetCursor to visit rows and cells without building the workbook model. Open the file, open the sheet, and loop over ReadRow() and ReadCell(). The sample sums numeric cells and prints Total: 263, including the numeric Id column. It uses a tiny workbook so the download is small; the same traversal works when the sheet contains more rows.

Streaming is useful when the operation is a single pass: aggregating amounts, filtering records or sending values to another destination. It is not the right abstraction for arbitrary edits across a workbook. The cursor moves forward, and values are valid only while it is positioned on their cell.

How do I work with very large Excel files?

Avoid loading objects you do not need. The example reads a cell kind before taking its numeric value and never constructs a list of rows. The variation additionally checks the column, so a numeric customer identifier in another column would not enter the amount total. For real imports, define which sheet and columns belong to the calculation rather than adding every number you encounter.

Keep both the reader and sheet cursor alive during traversal, and dispose them when finished. After a successful ReadRow(), row properties describe that row. After a successful ReadCell(), cell properties describe that cell until the cursor advances. Do not retain a text span and use it after another read. If a consumer needs an owned string, GetText() supplies one, with the allocation that entails.

Calling ReadRow() also skips cells you did not consume from the previous row. This permits a job that stops examining a row once it has the required columns. It does not permit jumping backward to a previously skipped cell. Rows and cells are delivered in stored order; out-of-order input is reported and is not sorted into a new table for you.

“Streaming” does not mean fixed memory for every input. The cursor documentation bounds memory by the largest token in the sheet plus the shared string table when text is read. Many distinct shared strings or one unusually long value can therefore affect memory even when you never retain a row yourself. Cell data is not held as a workbook model.

Can C# read an Excel file?

Yes. This recipe uses the published spreadsheets package to read XLSX directly. Its streaming namespace also supports XLSM and Flat OPC. Separate binary readers exist for XLS and XLSB; renaming a binary file with an XLSX extension does not make it an XLSX archive.

The cursor returns cached formula results, not newly evaluated formulas. If the source file has stale caches, the aggregation can be stale too. Decide whether the producer is responsible for calculating before delivery. Merely opening a file through this reader is not a calculation request.

Choose the data representation based on the next step. Numeric, Boolean and error properties avoid constructing strings. UTF-8 text spans are useful for immediate consumption; string-returning methods allocate. If your task needs named columns mapped to business objects, the typed row reader sits on streaming cursors and handles that mapping for you. Avoid adding ToList() when the purpose is to process an unbounded sequence one row at a time.

All onedocument recipes