OfficeIMO.CSV
3.0.0
Prefix Reserved
dotnet add package OfficeIMO.CSV --version 3.0.0
NuGet\Install-Package OfficeIMO.CSV -Version 3.0.0
<PackageReference Include="OfficeIMO.CSV" Version="3.0.0" />
<PackageVersion Include="OfficeIMO.CSV" Version="3.0.0" />
<PackageReference Include="OfficeIMO.CSV" />
paket add OfficeIMO.CSV --version 3.0.0
#r "nuget: OfficeIMO.CSV, 3.0.0"
#:package OfficeIMO.CSV@3.0.0
#addin nuget:?package=OfficeIMO.CSV&version=3.0.0
#tool nuget:?package=OfficeIMO.CSV&version=3.0.0
OfficeIMO.CSV - fluent CSV document model
OfficeIMO.CSV is a fluent, strongly typed CSV document model aligned with the OfficeIMO ecosystem. It supports in-memory transforms, streaming reads, schemas, validation, typed mapping, and AOT-friendly explicit selectors.
Install
dotnet add package OfficeIMO.CSV
Quick start
using OfficeIMO.CSV;
using System.Globalization;
new CsvDocument()
.WithDelimiter(';')
.WithCulture(CultureInfo.InvariantCulture)
.WithHeader("Name", "Age", "City")
.AddRow("Przemek", 36, "Mikolow")
.AddRow("Dominika", 30, "Mikolow")
.AddColumn("Bucket", row => row.AsInt32("Age") >= 35 ? "Senior" : "Regular")
.SortBy("Age")
.Filter(row => row.AsString("City") == "Mikolow")
.Save("people.csv", new CsvSaveOptions {
Delimiter = ';',
IncludeHeader = true,
FormulaInjectionPolicy = CsvFormulaInjectionPolicy.Escape,
NewLine = "\n"
});
What it does
- Keeps headers and rows as a first-class document model instead of ad hoc string arrays.
- Loads from files, streams, or text and saves through configurable delimiter, culture, encoding, and newline options.
- Supports single-character delimiters through
Delimiterand multi-character delimiters throughDelimiterText. - Reads and writes compressed CSV files with extension-based detection for gzip, deflate, Brotli, and zlib.
- Can escape formula-like values during save when producing CSV files that people will open in spreadsheet applications.
- Handles real-world import details such as duplicate headers, generated blank headers, null tokens, static metadata columns, custom date formats, comments, W3C
#Fields:headers, and mismatched row lengths. - Provides cancellation, progress callbacks, parse-error collection, field-length limits, quote normalization, and string interning for import pipelines.
- Supports
AddRow,AddColumn,RemoveColumn,SortBy,Filter, andTransform. - Provides schema inference and schema validation with required columns, typed columns, defaults, and custom rules.
- Maps rows to typed objects with explicit no-reflection mapping.
- Supports streaming mode for large files and explicit materialization when transforms are needed.
- Includes benchmark lanes against Dataplat/dbatools CSV, Sep, Sylvan, CsvHelper, and OfficeIMO fast paths.
Performance without giving up the document model
OfficeIMO.CSV has dedicated field-span, reusable-row, streaming DbDataReader,
projected-row, and trusted-text fast paths. The same package also keeps the
features expected from a document and ingestion model: schema inference and
validation, typed values, transforms, compressed files, malformed-input policy,
formula-injection protection, progress, cancellation, and diagnostics.
The focused table compares equivalent 25,000-row wide field-span reads,
projected-array writes, IDataReader writes, and prepared-text writes. Benchmark
preflight parses every typed field or compares every prepared text field, so a
library cannot win by merely producing the right row shape. Lower is faster;
differences below 5% should be treated as ties rather than ranking claims.
| Scenario | Variables | Host | Operation | Metric | OfficeIMO.CSV | CsvHelper | Dataplat.Dbatools.Csv | Sep | Sylvan.Data.Csv | Result |
|---|---|---|---|---|---|---|---|---|---|---|
| Wide DataReader CSV write | Contract=IDataReader, Format=CSV, Rows=25,000, Runner=BenchmarkDotNet local, Shape=wide, Snapshot=2026-07-14 | .NET 8 | Format and write rows | MeanMs | 1.00x (27ms) | n/a | 1.74x (47ms) | n/a | 0.99x (26ms) | OfficeIMO.CSV tied with Sylvan.Data.Csv |
| Wide field-span CSV read | Contract=field spans, Format=CSV, Rows=25,000, Runner=BenchmarkDotNet local, Shape=wide, Snapshot=2026-07-14 | .NET 8 | Read every field | MeanMs | 1.00x (2ms) | n/a | n/a | 1.06x (2ms) | 4.47x (9ms) | OfficeIMO.CSV fastest |
| Wide projected-array CSV write | Contract=projected object arrays, Format=CSV, Rows=25,000, Runner=BenchmarkDotNet local, Shape=wide, Snapshot=2026-07-14 | .NET 8 | Format and write rows | MeanMs | 1.00x (31ms) | 2.65x (82ms) | 1.43x (45ms) | n/a | n/a | OfficeIMO.CSV fastest |
| Wide validated text-row CSV write | Contract=preformatted text with escaping, Format=CSV, Rows=25,000, Runner=BenchmarkDotNet local, Shape=wide, Snapshot=2026-07-14 | .NET 8 | Validate and write rows | MeanMs | 1.00x (17ms) | 1.33x (23ms) | 1.25x (21ms) | 1.20x (20ms) | 0.99x (17ms) | OfficeIMO.CSV tied with Sylvan.Data.Csv |
These are local snapshots, not universal rankings. Runtime, CPU, input
shape, quoting, encoding, storage, warm-up, and consumer behavior all matter;
results will vary. See the full benchmark harness
for CsvHelper, Dataplat/dbatools, LumenWorks, Sep, Sylvan, DataTable, and
DbDataReader lanes and the exact commands used to reproduce them.
Schema example
var document = CsvDocument.Load("input.csv")
.EnsureSchema(schema => schema
.Column("Id").AsInt32().Required()
.Column("Name").AsString().Required()
.Column("Age").AsInt32().Optional())
.ValidateOrThrow();
Collect validation errors without throwing when an import pipeline should report all bad rows:
var document = CsvDocument.Load("input.csv")
.EnsureSchema(schema => schema
.Column("Id").AsInt32().Required()
.Column("Name").AsString().Required()
.Column("Age").AsInt32().Optional()
.Column("Active").AsBoolean().WithDefault(true));
document.Validate(out var errors);
foreach (var error in errors) {
Console.WriteLine($"{error.RowIndex}:{error.ColumnName} - {error.Message}");
}
Use ConvertUsing when a column needs domain-specific conversion before it becomes a DataTable or IDataReader value:
var document = CsvDocument.Load("input.csv")
.EnsureSchema(schema => schema
.Column("Priority")
.AsInt32()
.ConvertUsing(value => string.Equals(Convert.ToString(value), "high", StringComparison.OrdinalIgnoreCase) ? 10 : 1));
DataTable table = document.ToDataTable();
Infer a schema from sampled rows when the incoming file should define the import contract:
var document = CsvDocument.Load("input.csv", new CsvLoadOptions {
DateTimeFormats = new[] { "dd-MMM-yyyy" }
});
CsvSchema inferred = document.InferSchema(sampleSize: 1000);
document.EnsureInferredSchema()
.ValidateOrThrow();
Typed mapping
Mapping is explicit and delegate-based, so it stays predictable for trimming and NativeAOT-sensitive applications.
using OfficeIMO.CSV;
List<Person> people = CsvDocument.Load("people.csv")
.Map<Person>(map => map
.FromColumn<int>("Id", (person, value) => {
person.Id = value;
return person;
})
.FromColumn<string>("Name", (person, value) => {
person.Name = value;
return person;
})
.FromColumn<int>("Age", (person, value) => {
person.Age = value;
return person;
})
.FromColumn<string>("City", (person, value) => {
person.City = value;
return person;
}))
.ToList();
public sealed class Person {
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
public string City { get; set; } = "";
}
For immutable models, return a new instance from each assignment:
using OfficeIMO.CSV;
var people = CsvDocument.Load("people.csv")
.Map<PersonRecord>(map => map
.FromColumn<int>("Id", (person, value) => person with { Id = value })
.FromColumn<string>("Name", (person, value) => person with { Name = value }))
.ToList();
public sealed record PersonRecord {
public int Id { get; init; }
public string Name { get; init; } = "";
}
Streaming and materializing
Use streaming mode when the caller only needs forward-only row processing.
foreach (var row in CsvDocument.Load("large.csv", new CsvLoadOptions {
Mode = CsvLoadMode.Stream,
HasHeaderRow = true,
TrimWhitespace = true
}).AsEnumerable()) {
int id = row.AsInt32("Id");
string? status = row.AsString("Status");
Console.WriteLine($"{id}: {status}");
}
Transforms such as SortBy, Filter, and AddColumn require in-memory mode. Materialize deliberately when that is the desired tradeoff:
var transformed = CsvDocument.Load("large.csv", new CsvLoadOptions {
Mode = CsvLoadMode.Stream
})
.Materialize()
.AddColumn("ImportedUtc", _ => DateTime.UtcNow)
.Filter(row => row.AsString("Status") == "Ready")
.SortBy(row => row.AsInt32("Id"));
transformed.Save("ready.csv");
Use CreateDataReader when the next hop expects an ADO.NET reader, such as DataTable.Load or a provider bulk-copy API. Schema inference can expose typed columns while the rows remain forward-only:
using System.Data;
var document = CsvDocument.Load("large.csv", new CsvLoadOptions {
Mode = CsvLoadMode.Stream
});
using var reader = document.CreateDataReader(new CsvDataReaderOptions {
InferSchema = true,
SchemaSampleSize = 1000
});
var table = new DataTable();
table.Load(reader);
Real-world headers
CSV exports often contain blank or repeated header names. By default, blank headers are generated as H1, H2, and duplicate names are renamed with suffixes so name-based row access stays unambiguous:
var document = CsvDocument.Parse("Name,Name\nAlpha,Beta\n");
Console.WriteLine(string.Join(", ", document.Header));
// Name, Name_2
Use DuplicateHeaderBehavior when a pipeline needs to preserve source names exactly or reject ambiguous files:
var strict = new CsvLoadOptions {
DuplicateHeaderBehavior = CsvDuplicateHeaderBehavior.Throw
};
CsvDocument.Load("input.csv", strict);
Append static metadata columns during import when a database or audit pipeline needs source context on every row:
var document = CsvDocument.Load("input.csv", new CsvLoadOptions {
StaticColumns = new Dictionary<string, object?> {
["SourceFile"] = "input.csv",
["ImportedUtc"] = DateTime.UtcNow
}
});
Use NullValue and DateTimeFormats when a CSV producer uses explicit null tokens or non-default date shapes:
var document = CsvDocument.Load("input.csv", new CsvLoadOptions {
NullValue = "<null>",
DateTimeFormats = new[] { "dd-MMM-yyyy", "yyyyMMdd-HHmmss" }
});
DateTime created = document.AsEnumerable().First().AsDateTime("Created");
The parser defaults to lenient quoted-field handling for compatibility with common PowerShell CSV imports. Use strict mode when malformed quotes should fail the import:
var document = CsvDocument.Load("input.csv", new CsvLoadOptions {
QuoteParsingMode = CsvQuoteParsingMode.Strict
});
Use DelimiterText for multi-character delimiters such as || or ::. Quoted fields can still contain the delimiter text:
var document = CsvDocument.Parse(
"Name||Value\nAlpha||\"one||two\"\n",
new CsvLoadOptions { DelimiterText = "||" });
document.Save("pipes.csv", new CsvSaveOptions {
DelimiterText = "||",
NewLine = "\n"
});
Long-running import paths can opt into cancellation and progress reporting without changing the document model:
using var cancellation = new CancellationTokenSource();
var document = CsvDocument.Load("large.csv", new CsvLoadOptions {
Mode = CsvLoadMode.Stream,
CancellationToken = cancellation.Token,
ProgressReportInterval = 10_000,
ProgressCallback = progress =>
Console.WriteLine($"{progress.RecordsRead} records read")
});
Export options
CSV output supports null tokens, date/time formatting, UTC conversion, append, no-clobber checks, compression, quoting, encoding, and formula escaping:
CsvDocument.Load("input.csv")
.Save("output.csv.gz", new CsvSaveOptions {
NullValue = "<null>",
DateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ",
UseUtc = true,
CompressionType = CsvCompressionType.Auto,
FormulaInjectionPolicy = CsvFormulaInjectionPolicy.Escape,
NewLine = "\n"
});
Append without rewriting the header:
CsvDocument.Load("next.csv")
.Save("combined.csv", new CsvSaveOptions {
Append = true,
IncludeHeader = false
});
Objects and ad hoc data
FromObjects is useful for small exports from anonymous objects, DTOs, or dictionaries:
var rows = new[] {
new { Name = "Alpha", Count = 10, Active = true },
new { Name = "Beta", Count = 20, Active = false }
};
CsvDocument.FromObjects(rows)
.Save("summary.csv");
Use direct object writing for larger exports when the caller does not need to materialize a CsvDocument first. The same save options are honored, including null tokens, date/time formatting, UTC conversion, compression, append, and no-clobber checks:
CsvDocument.SaveObjects("summary.csv.gz", rows, new CsvSaveOptions {
NullValue = "<null>",
DateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ",
UseUtc = true,
CompressionType = CsvCompressionType.Auto
});
When the caller already has projected arrays, pass the shared schema once. The writer validates every row width without repeating column-name validation:
object?[][] projectedRows = {
new object?[] { "Alpha", 10, true },
new object?[] { "Beta", 20, false }
};
using var output = File.CreateText("summary.csv");
using var csv = new CsvObjectWriter(output);
csv.WriteRows(new[] { "Name", "Count", "Active" }, projectedRows);
Use WriteTextRows for arrays that are already culture-formatted; CSV escaping
and row-width validation still apply.
Parse text when a service receives CSV payloads without a temporary file:
string payload = "Name,Amount\nAlpha,10\nBeta,20";
var document = CsvDocument.Parse(payload)
.AddColumn("Currency", _ => "EUR");
string normalized = document.ToString(new CsvSaveOptions {
Delimiter = ',',
IncludeHeader = true
});
Boundaries
- This package owns CSV parsing, writing, transforms, and validation.
DelimiterTextsupports explicit multi-character delimiters. Delimiter auto-detection is still character-candidate based.- Parallel CSV-to-database import is intentionally outside this package; database bulk copy and provider behavior belong in DbaClientX or the consuming data-access layer.
- Reader integration belongs in
OfficeIMO.Reader.Csv. - Excel workbook behavior belongs in
OfficeIMO.Excel.
Targets and license
- Targets:
netstandard2.0,net8.0,net10.0,net472. - License: MIT.
- Repository: EvotecIT/OfficeIMO
Dependency footprint
- External: No third-party CSV engine.
System.Buffersand .NET Framework reference assemblies support compatibility targets. - OfficeIMO:
OfficeIMO.Drawing. Parsing, streaming, schemas, transforms, compression, and object mapping are first-party.
See the complete OfficeIMO package map for related formats and conversion paths.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 is compatible. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.7.2
- OfficeIMO.Drawing (>= 3.0.0)
- System.Buffers (>= 4.5.1)
-
.NETStandard 2.0
- OfficeIMO.Drawing (>= 3.0.0)
- System.Buffers (>= 4.5.1)
-
net10.0
- OfficeIMO.Drawing (>= 3.0.0)
-
net8.0
- OfficeIMO.Drawing (>= 3.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on OfficeIMO.CSV:
| Package | Downloads |
|---|---|
|
OfficeIMO.Reader.Csv
CSV/TSV adapters for OfficeIMO.Reader and OfficeIMO.Excel. |
|
|
OfficeIMO.Reader.Excel
Excel workbook adapter for OfficeIMO.Reader.Core. |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on OfficeIMO.CSV:
| Repository | Stars |
|---|---|
|
EvotecIT/PSWriteOffice
PowerShell Module to create and edit Microsoft Word, Microsoft Excel, and Microsoft PowerPoint documents without having Microsoft Office installed.
|
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 343 | 7/20/2026 |
| 2.0.1 | 289 | 7/14/2026 |
| 2.0.0 | 142 | 7/14/2026 |
| 0.1.55 | 1,174 | 7/9/2026 |
| 0.1.54 | 888 | 7/8/2026 |
| 0.1.53 | 1,097 | 7/5/2026 |
| 0.1.52 | 802 | 7/4/2026 |
| 0.1.51 | 848 | 6/27/2026 |
| 0.1.50 | 684 | 6/27/2026 |
| 0.1.49 | 442 | 6/24/2026 |
| 0.1.48 | 239 | 6/23/2026 |
| 0.1.47 | 359 | 6/21/2026 |
| 0.1.46 | 180 | 6/16/2026 |
| 0.1.45 | 379 | 6/16/2026 |
| 0.1.44 | 232 | 6/15/2026 |
| 0.1.43 | 266 | 6/13/2026 |
| 0.1.42 | 295 | 6/12/2026 |
| 0.1.41 | 200 | 6/9/2026 |
| 0.1.40 | 206 | 6/5/2026 |
| 0.1.39 | 356 | 5/27/2026 |