TriasDev.Tabular¶
Fast, low-memory reading and writing of csv, Excel (.xlsx) and OpenDocument (.ods) files for .NET — with no dependencies.
The library has two halves:
- Import — reads xlsx, ods and csv, alone or inside zip, tar and gzip archives; profiles every column over every row (types, emptiness, uniqueness, ranges, the rows that do not fit); imports typed rows through a column mapping a person confirmed, with errors that name their row, column and code.
- Export — writes csv, xlsx, ods and a zip of csv sheets, streaming and asynchronously into any stream — a file, a blob, an ASP.NET Core response — with typed columns, cell styles, frozen headers and auto-filters. What it writes reads back through its own import; a value a format cannot hold exactly is refused, never rounded.
Multi-million-row files are read in seconds, memory stays flat in both directions as files grow, and everything is built on the base class library alone: no third-party packages.
Reading — declare the fields once, profile the file, build a mapping plan from its headers; then import typed rows or located errors through it:
// The fields, declared once: they build the schema and read the values.
TextImportField name = ImportField.Text("name").Require().MaxLength(100);
TextImportField country = ImportField.Text("country").ExactLength(2);
DateImportField signedOn = ImportField.Date("signed_on");
DecimalImportField amount = ImportField.Decimal("amount").Require();
ImportSchema schema = new() { Fields = [name, country, signedOn, amount] };
// 1. Profile the file, and build the plan from its headers.
FileProfile profile;
using (FileStream file = File.OpenRead(path))
using (ITabularCursor cursor = TabularFile.Open(file, Path.GetFileName(path)))
{
profile = TabularAnalyzer.Analyze(cursor);
}
MappingPlan plan = MappingPlan.ByHeader(profile.Sheets[0], schema, culture: "de-DE");
Writing — declare the columns once, with a style rule and a frozen, filtered header, and stream chunks of objects into a workbook (exporting):
private static readonly CellStyle Late = new() { Fill = CellColor.Parse("#FFC7CE") };
private static readonly CellStyle Header = new() { Fill = CellColor.Parse("#1F4E78"), Font = new CellFont { Bold = true, Color = CellColor.Parse("#FFFFFF") } };
// Declared once, kept in a static field, shared by any number of concurrent writes.
public static readonly TabularExport<Order> Export = TabularExport.For<Order>()
.Column("Id", o => o.Id, width: 8)
.Column("Customer", o => o.Customer, width: 28)
.Column("Placed", o => o.Placed)
.Column("Amount", o => o.Amount, width: 12)
.Column("Status", o => o.Status, width: 10, style: status => status == "late" ? Late : null)
.Column("Paid", o => o.Paid)
.Sheet(new SheetOptions { HeaderStyle = Header, FreezeRows = 1, AutoFilter = true })
.Build();
public static async Task<long> WriteChunksAsync(Stream stream, IAsyncEnumerable<IReadOnlyList<Order>> chunks, CancellationToken cancellationToken) =>
await Export.WriteAsync(stream, TabularFormat.Xlsx, "Orders", chunks, cancellationToken: cancellationToken);
Choose your path¶
I am adding an upload to my application¶
A user uploads a spreadsheet; you profile it, let them map its columns to your fields, check the mapping, and import typed rows or precise errors.
- Getting started — install, profile, map, check, import; write a file
- How it works — two independent reads of the file, facts against suggestions
- Importing — the run, batches, rules, translated fields, field alternatives, the precheck and the review
- Error codes — every code a row error or an exception carries
I just need to read big files fast¶
A forward-only cursor over rows of typed cells, for xlsx, ods, csv, zip and tar archives of them, and gzip-compressed files.
- Formats — what each kind of file reads as, and how malformed csv is repaired
- Performance — the library's own numbers
- Benchmarks — against Sylvan, Sep, CsvHelper, ExcelDataReader, MiniExcel, ClosedXML, NPOI and EPPlus
I need to hand files back¶
Csv, xlsx, ods or a zip of csv sheets, written row by row, from objects declared once, or by column — styled, streamed into a response or a blob, in flat memory.
- Exporting — the writer, declared exports, data by column, styles, sheet layout, where the file goes, failures
- Formats — what each format holds when written
- Benchmarks — against CsvHelper, Sep, Sylvan, LargeXlsx, SpreadCheetah and MiniExcel
I need to know what it will refuse¶
- Bounds — every ceiling that protects a server from a hostile file, and every limit a written file is held to
- Streams, cancellation, progress and cultures
- Error codes — including the
write.*codes for a value a format cannot hold - Known issues — behaviour at the edges, with what would make each one matter, and what does not come back exactly as written
For AI agents¶
The documentation is also published for language models, following llms.txt:
llms.txt is a short map of the library, and llms-full.txt is every
page in one Markdown file. Each page is also available as Markdown next to its HTML.
Source¶
github.com/TriasDev/tabular · MIT licence · NuGet