JetDatabaseReader 2.2.0
dotnet add package JetDatabaseReader --version 2.2.0
NuGet\Install-Package JetDatabaseReader -Version 2.2.0
<PackageReference Include="JetDatabaseReader" Version="2.2.0" />
<PackageVersion Include="JetDatabaseReader" Version="2.2.0" />
<PackageReference Include="JetDatabaseReader" />
paket add JetDatabaseReader --version 2.2.0
#r "nuget: JetDatabaseReader, 2.2.0"
#:package JetDatabaseReader@2.2.0
#addin nuget:?package=JetDatabaseReader&version=2.2.0
#tool nuget:?package=JetDatabaseReader&version=2.2.0
JetDatabaseReader
Pure-managed .NET library for reading Microsoft Access JET databases — no OleDB, ODBC, or ACE/Jet driver installation required.
v2.0 introduced typed DataTables and typed streaming by default. v2.1 adds structured schema types (
ColumnSize,TableStat,FirstTableResult). v2.2 cleans up theTableResultAPI (ReadTableAsStrings,ToDataTable, ACCDB encryption fix). See CHANGELOG.md and the migration guide for breaking changes.
Features
| ✅ No native dependencies | Pure C# — runs anywhere .NET runs |
| ✅ Jet3 & Jet4 / ACE | Access 97 through Access 2019 (.mdb / .accdb) |
| ✅ Typed by default | int, DateTime, decimal, Guid — not just strings |
| ✅ All column types | Text, Integer, Currency, Date/Time, GUID, MEMO, OLE Object, Decimal |
| ✅ Streaming API | Process millions of rows without loading the whole file |
| ✅ Async support | Full Task<T>-based async for all major operations |
| ✅ Page cache | 256-page LRU cache (~1 MB, configurable) |
| ✅ Fluent query | Query().Where().Take().Execute() — typed and string chains |
| ✅ Progress reporting | IProgress<int> callbacks on all long operations |
| ✅ Non-Western text | Code page auto-detected from the database header |
| ✅ OLE Objects | Detects embedded JPEG, PNG, PDF, ZIP, DOC, RTF |
Installation
dotnet add package JetDatabaseReader
Install-Package JetDatabaseReader
NuGet target compatibility
JetDatabaseReader targets netstandard2.0, which is consumed by every current .NET surface:
| Consumer | Minimum version |
|---|---|
| .NET Framework | 4.6.1 |
| .NET Core | 2.0 |
| .NET | 5 / 6 / 7 / 8 / 9 |
| Mono / Xamarin | All |
| Unity | 2018.1+ |
| UWP | 10.0.16299+ |
Quick Start
using JetDatabaseReader;
using var reader = AccessReader.Open("database.mdb");
List<string> tables = reader.ListTables();
Console.WriteLine($"Found {tables.Count} tables: {string.Join(", ", tables)}");
DataTable dt = reader.ReadTable("Orders");
foreach (DataRow row in dt.Rows)
{
int id = (int)row["OrderID"];
var date = (DateTime)row["OrderDate"];
decimal amt = (decimal)row["Freight"];
Console.WriteLine($"#{id} {date:yyyy-MM-dd} {amt:C}");
}
Reading Data
Typed DataTable — recommended
DataTable dt = reader.ReadTable("Products");
// dt.Columns["ProductID"].DataType == typeof(int)
// dt.Columns["UnitPrice"].DataType == typeof(decimal)
// dt.Columns["Discontinued"].DataType == typeof(bool)
String DataTable — compatibility
DataTable dt = reader.ReadTableAsStringDataTable("Products");
// every column is typeof(string)
Table preview with schema — typed
TableResult preview = reader.ReadTable("Products", maxRows: 20);
foreach (TableColumn col in preview.Schema)
{
Type clrType = col.Type; // e.g. typeof(int), typeof(string)
string display = col.Size.ToString(); // e.g. "4 bytes", "255 chars", "LVAL"
Console.WriteLine($"{col.Name}: {clrType.Name} ({col.Size})");
}
// Convert to DataTable with CLR-typed columns
DataTable dt = preview.ToDataTable();
// dt.Columns["UnitPrice"].DataType == typeof(decimal)
Table preview with schema — strings
StringTableResult preview = reader.ReadTableAsStrings("Products", maxRows: 20);
string firstCell = preview.Rows[0][0]; // always a string
// Convert to DataTable — all columns typeof(string)
DataTable dt = preview.ToDataTable();
Streaming Large Tables
Typed streaming — recommended
var progress = new Progress<int>(n => Console.Write($"\r{n:N0} rows"));
foreach (object[] row in reader.StreamRows("BigTable", progress))
{
int id = (int)row[0];
decimal val = row[2] == DBNull.Value ? 0m : (decimal)row[2];
}
String streaming — compatibility
foreach (string[] row in reader.StreamRowsAsStrings("BigTable"))
Console.WriteLine(string.Join(", ", row));
Null values in typed rows surface as DBNull.Value.
Fluent Query API
// Typed chain
object[] order = reader.Query("Orders")
.Where(row => row[2] is DateTime d && d.Year == 2024)
.Take(10)
.FirstOrDefault();
int count = reader.Query("OrderDetails")
.Where(row => row[3] is decimal p && p > 100m)
.Count();
// String chain
IEnumerable<string[]> recent = reader.Query("Orders")
.WhereAsStrings(row => row[2].StartsWith("2024"))
.Take(50)
.ExecuteAsStrings();
Async Operations
List<string> tables = await reader.ListTablesAsync();
DataTable dt = await reader.ReadTableAsync("Orders");
TableResult typed = await reader.ReadTableAsync("Orders", 50);
StringTableResult str = await reader.ReadTableAsStringsAsync("Orders", 50);
DatabaseStatistics stats = await reader.GetStatisticsAsync();
Dictionary<string, DataTable> all = await reader.ReadAllTablesAsync();
Dictionary<string, DataTable> allStr = await reader.ReadAllTablesAsStringsAsync();
Bulk Operations
// Typed columns
Dictionary<string, DataTable> all = reader.ReadAllTables(
new Progress<string>(t => Console.WriteLine($"Reading {t}...")));
// String columns (compatibility)
Dictionary<string, DataTable> allStr = reader.ReadAllTablesAsStrings();
Statistics & Metadata
foreach (ColumnMetadata col in reader.GetColumnMetadata("Orders"))
Console.WriteLine($"{col.Ordinal}. {col.Name} — {col.TypeName} ({col.ClrType.Name})");
// Table-level stats (single catalog scan)
foreach (TableStat s in reader.GetTableStats())
Console.WriteLine($"{s.Name}: {s.RowCount:N0} rows, {s.ColumnCount} cols");
// First table preview + total table count
FirstTableResult first = reader.ReadFirstTable();
Console.WriteLine($"First: {first.TableName} ({first.TableCount} tables total)");
DatabaseStatistics s = reader.GetStatistics();
Console.WriteLine($"Version: {s.Version}");
Console.WriteLine($"Size: {s.DatabaseSizeBytes / 1024 / 1024} MB");
Console.WriteLine($"Tables: {s.TableCount} Rows: {s.TotalRows:N0}");
Console.WriteLine($"Cache hit: {s.PageCacheHitRate}%");
Configuration
var options = new AccessReaderOptions
{
PageCacheSize = 512, // pages in LRU cache (default: 256)
ParallelPageReadsEnabled = true, // parallel I/O (default: false)
DiagnosticsEnabled = false, // verbose logging (default: false)
ValidateOnOpen = true, // format check on open (default: true)
FileAccess = FileAccess.Read, // default
FileShare = FileShare.Read, // default: others may read, writes blocked
// FileShare = FileShare.ReadWrite // use when Access has the file open
};
using var reader = AccessReader.Open("database.mdb", options);
Error Handling
try { var dt = await reader.ReadTableAsync("Orders"); }
catch (FileNotFoundException) { /* file missing */ }
catch (NotSupportedException) { /* encrypted / password-protected */ }
catch (InvalidDataException) { /* corrupt or non-JET file */ }
catch (JetLimitationException) { /* deleted-column gap, numeric overflow */ }
catch (ObjectDisposedException) { /* reader already disposed */ }
Limitations
| ❌ Encrypted databases | Remove password in Access (File › Info › Encrypt with Password) |
| ❌ Attachment fields (0x11) | Rare type added in Access 2007 |
| ❌ Linked tables | Only local tables are listed |
| ❌ Overflow rows | Rows spanning multiple pages are skipped |
| ❌ Write operations | Read-only library |
Migration from v1
// Open
var r = new JetDatabaseReader("db.mdb"); // v1 ❌
var r = AccessReader.Open("db.mdb"); // v2 ✅
// Typed DataTable
var dt = r.ReadTableAsDataTable("Orders"); // v1 — string columns
var dt = r.ReadTable("Orders"); // v2 ✅ typed
var dt = r.ReadTableAsStringDataTable("Orders"); // v2 compat
// Preview — typed rows (v2.2: Rows is now List<object[]>, was List<List<string>>)
TablePreviewResult t = r.ReadTable("T", 10); // v2.0.0 ❌
TableResult t = r.ReadTable("T", 10); // v2.0.1–v2.1 ✅ (Rows was List<List<string>>)
TableResult t = r.ReadTable("T", 10); // v2.2 ✅ Rows is now List<object[]>
// Preview — string rows (v2.2: new dedicated API)
StringTableResult s = r.ReadTableAsStrings("T", 10); // v2.2 ✅
string val = s.Rows[0][2]; // always string
// bool overload removed (v2.2)
TableResult t = r.ReadTable("T", 10, typedValues: true); // v2.1 ❌ removed
TableResult t = r.ReadTable("T", 10); // v2.2 ✅
// ToDataTable (v2.2: new on both result types)
DataTable dtTyped = r.ReadTable("T", 100).ToDataTable(); // CLR-typed columns
DataTable dtStr = r.ReadTableAsStrings("T", 100).ToDataTable(); // string columns
// Schema properties (v2.0.1 → v2.1.0)
col.TypeName // v2.0.1 ❌ — string e.g. "Long Integer"
col.Type // v2.1.0 ✅ — System.Type e.g. typeof(int)
col.SizeDesc // v2.0.1 ❌ — string e.g. "4 bytes"
col.Size // v2.1.0 ✅ — ColumnSize struct (.Value, .Unit, .ToString())
// Table stats (v2.1.0)
foreach (var (n, r, c) in reader.GetTableStats()) // v2.0 ❌ tuple
foreach (TableStat s in reader.GetTableStats()) // v2.1 ✅ named type
// First table (v2.1.0 → v2.2.0: base class changed)
TableResult r = reader.ReadFirstTable(); // v2.0 ❌
FirstTableResult r = reader.ReadFirstTable(); // v2.1+ ✅ + r.TableCount
// Note: FirstTableResult now extends StringTableResult (v2.2)
// Streaming
foreach (string[] row in r.StreamRows("T")) // v1
foreach (object[] row in r.StreamRows("T")) // v2 ✅ typed
foreach (string[] row in r.StreamRowsAsStrings("T")) // v2 compat
// Bulk
var all = r.ReadAllTables(); // v1 — string cols / v2 ✅ typed
var all = r.ReadAllTablesAsStrings(); // v2 compat
Full details in CHANGELOG.md.
How It Works
Based on the mdbtools format specification. The library parses JET pages directly:
- Page 0 — header: Jet3/Jet4 detection, code page, encryption flag
- Page 2 —
MSysObjectscatalog: table names → TDEF page numbers - TDEF pages — table definition chains: column descriptors + names
- Data pages — row slot arrays → null mask + fixed/variable fields
- LVAL pages — long-value chains for MEMO and OLE fields
Contributing
Issues and pull requests welcome at github.com/diegoripera/JetDatabaseReader.
License
MIT — see LICENSE for details.
| 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 was computed. 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 was computed. 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 was computed. 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. |
-
.NETStandard 2.0
- System.Text.Encoding.CodePages (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v2.0.0 — Breaking changes: ReadTable() now returns typed DataTable; StreamRows() now returns IEnumerable<object[]>; factory method AccessReader.Open() required. See CHANGELOG.md for full details.