Skip to content

feat!: .NET 10, streaming writer, fixes, opt-in WriteExcelAsync API - #32

Merged
samerzughul merged 1 commit into
mainfrom
muhannad/net10-review
Oct 12, 2026
Merged

samerzughul merged 1 commit into
mainfrom
muhannad/net10-review

Conversation

@mmalkhatib

Copy link
Copy Markdown
Contributor

Retargets the library to net10.0 + net8.0 and rewrites its internals as a streaming writer. Every existing method keeps its signature, column order and cell types. Merging publishes 10.0.0 to nuget.org.

Existing API: same output, but faster and fixed

WriteExcel / ExportToExcel / GenerateExcel now run on a writer that streams the xlsx parts straight into a zip, instead of building the whole sheet as an in-memory XML tree.

200k rows × 10 columns 8.0.6 this PR
Time 2.9 s 0.76 s
Allocated 1.4 GB 80 MB
Peak heap 945 MB 13 MB

Equivalence check: I compared 9 cases covering every overload and all supported types, about 15k cells, against 8.0.6, checking type, style and value for each cell. Numbers, dates, decimals and floats are the same text.

Differences from 8.0.6. Each was reproduced against 8.0.6. Where it says Excel refuses a file, the file was opened in Excel and failed.

  • Control characters (\x01, \v …) in a string: used to throw XmlException mid-export. Now stripped.
  • NaN/Infinity, text over 32,767 characters, or a type with no exportable properties: used to produce a file Excel refuses to open. Now written as text, truncated, and written as a valid empty sheet, respectively.
  • Frozen header: the pane was written outside <sheetView>, so Excel ignored it. It now works.
  • Column widths: every <col> element pointed at column 1, which made column A 4 characters wide. Widths are no longer forced.
  • Empty strings: were written as a newline plus 10 spaces. Now an empty cell.
  • An indexer property: threw TargetParameterCountException. Now skipped.
  • Dictionary rows: values were matched by position, not by key, and an empty list threw. Now matched by key; an empty list gives an empty sheet.
  • Column letters from column 677 on: were wrong ("A A"). Now correct.
  • Dictionary sheets: now include a stylesheet.

New opt-in API: WriteExcelAsync

A separate method name, so existing callers are unaffected:

  • Columns come out in the order given, from a property → header map or as computed ExcelColumn<T> lambdas.
  • Rows can be IDictionary<string, object>, ExpandoObject or IDictionary<string, string>.
  • IAsyncEnumerable<T> sources are supported (e.g. EF AsAsyncEnumerable()), with cancellation.
  • Cells are typed: real booleans, dates, DateOnly, enums by name, Guid.
  • Options: auto-fit widths, bold header, autofilter, date formats, sheet name.

Full details are in the README.

Compatibility notes

  • DocumentFormat.OpenXml: the library no longer uses it, but it stays a dependency, now 3.5.1 instead of 2.13.1. Accounting, Checkout, Rates and Warehouse have stray using DocumentFormat.OpenXml… lines that compile only because this package brings it in. Dropping it broke all four builds.
  • Traxis consumers: I built all five (Accounting, Checkout, Rates, Ship, Warehouse) from develop against this package. All build with no source changes. Ship resolves OpenXml 3.5.1 alongside ClosedXML 0.105.0, with no range warnings.
  • Older frameworks: a project that can't move to .NET 8 can stay on 2.0.5 (netstandard2.0).

CI

Targets net10.0 and net8.0. The original WriteExcel/ExportToExcel/GenerateExcel
keep their signatures and output; they now run on a streaming writer that
writes the xlsx parts straight into a zip instead of building an XML tree.
200k rows x 10 columns: 2.9 s -> 0.76 s, peak heap 945 MiB -> 13 MiB.

Fixed (each verified against the published 8.0.6):
- control characters in a string threw XmlException mid-export
- NaN/Infinity, text over 32,767 chars, and types with no exportable
  properties produced files Excel refuses to open
- the frozen header pane was written outside sheetView and ignored
- every <col> targeted column 1, forcing column A to width 4
- empty strings were written as a newline plus indentation
- an indexer property threw TargetParameterCountException
- dictionary rows were matched by position, not key; an empty list threw
- column letters were wrong from column 677 on

New opt-in WriteExcelAsync: columns in the order given, computed columns
(ExcelColumn<T>), dictionary/ExpandoObject rows, IAsyncEnumerable sources,
typed booleans/dates/DateOnly/enums, auto-fit, bold header, autofilter,
cancellation.

DocumentFormat.OpenXml is no longer used but stays a dependency (now 3.5.1):
four Traxis services compile only because this package brings it in.
@coderabbitai

coderabbitai Bot commented Oct 12, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: simplify9/coderabbit/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 5cb1a66f-497e-4ebc-bcab-aec546645c0a

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@samerzughul
samerzughul merged commit 62a368c into main Oct 12, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants