Getting started¶
Install¶
The package targets .NET 8 and .NET 10 and references nothing but the base class library.
Read a file¶
TabularFile.Open decides the format from the file's bytes — csv, xlsx, ods, or a zip archive of
them — and returns a cursor over its sheets and rows:
using TriasDev.Tabular;
using FileStream file = File.OpenRead("orders.xlsx");
using ITabularCursor cursor = TabularFile.Open(file, "orders.xlsx");
foreach (SheetInfo sheet in cursor.Sheets)
{
cursor.MoveToSheet(sheet.Index);
while (cursor.ReadRow())
{
ReadOnlySpan<RawCell> row = cursor.CurrentRow; // typed cells: text, number, date, boolean
}
}
A row is a view over a reused buffer: read what you need from it before the next ReadRow.
Import into your own type¶
The steps an upload goes through, from
samples/TriasDev.Tabular.Samples.Import,
which the build compiles — so the code on this page is code that works.
Declare the fields once. They are both the schema and the accessors that read a row:
// The fields, declared once: they build the schema and read the values.
TextField name = ImportField.Text("name").Require().MaxLength(100);
TextField country = ImportField.Text("country").ExactLength(2);
DateField signedOn = ImportField.Date("signed_on");
DecimalField amount = ImportField.Decimal("amount").Require();
TargetSchema schema = new() { Fields = [name, country, signedOn, amount] };
Profile the file and build a plan from its headers. In an application, a person confirms or changes the plan on a mapping screen; the profile is what that screen shows:
// 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 = new TabularAnalyzer().Analyze(cursor);
}
MappingPlan plan = MappingPlan.ByHeader(profile.Sheets[0], schema, culture: "de-DE");
Check the plan against the profile, before the file is read again:
// 2. Judge the plan against the profile, before reading the file again.
PrecheckResult check = MappingPrecheck.Check(plan, schema, profile);
foreach (PrecheckFinding finding in check.Findings)
{
Console.WriteLine($"precheck: {finding.Severity} {finding.Code} on {finding.TargetFieldName}");
}
if (!check.CanImport)
{
return 1;
}
Import: typed rows, or errors that name their row, column and code:
// 3. Import: typed rows, or errors that point at their row and column.
using FileStream again = File.OpenRead(path);
using ImportRun<Customer> run = TabularImporter.Import(
again, Path.GetFileName(path), plan, schema,
row => new Customer(row[name]!, row[country], row[signedOn], row[amount]!.Value));
foreach (ImportOutcome<Customer> outcome in run)
{
if (outcome.HasErrors)
{
foreach (RowError error in outcome.Errors)
{
Console.WriteLine($"row {error.RowNumber}, {error.TargetFieldName}: {error.Code} ({error.RawValue})");
}
continue;
}
Customer customer = outcome.Value;
Console.WriteLine(string.Create(
System.Globalization.CultureInfo.InvariantCulture,
$"imported {customer.Name}, {customer.Country}, {customer.SignedOn:yyyy-MM-dd}, {customer.Amount}"));
}
Console.WriteLine($"{run.Summary.RowsProduced} imported, {run.Summary.RowsFailed} failed");
Run it with dotnet run --project samples/TriasDev.Tabular.Samples.Import: the sample file has a date
that is not one and an empty required amount, and both come back as row errors.
Next¶
- How it works — why analysis and import are two reads, and what a profile measures
- Importing — batches, rules of your own, translated fields, the precheck in depth
- Formats — what csv, xlsx, ods and zip files read as