Fully managed .NET library for reading and writing Microsoft Access (JET/ACE) databases — no OleDB, ODBC, or ACE/Jet driver installation required.
Use it to query, migrate, or generate .mdb and .accdb files directly from your .NET applications and tools, without installing Microsoft Access.
- Installation
- Features
- Quick Start
- Reading Data
- Writing Data
- Documentation
- Encryption Support
- Limitations
| Support | Feature | Description |
|---|---|---|
| ✅ | Pure managed .NET | Uses .NET APIs; no OleDB, ODBC, ACE/Jet driver, library P/Invoke, or native helper binaries |
| Partial | JET/ACE formats | Jet3, Jet4 and ACE .mdb / .accdb files; feature and generation coverage is incomplete. Pre-Jet3 formats are not supported. See the validation matrix. |
| ✅ | Read & write | Create databases and tables; insert/update/delete rows; add/drop/rename columns |
| ✅ | Typed values | int, DateTime, decimal, Guid, MEMO, OLE, Hyperlink — not just strings |
| ✅ | POCO + LINQ | Rows<T>("...", o => …) auto-infers an index; FromIndex<T>(...) to override; async LINQ (Where/Take/FirstOrDefaultAsync/…) over IAsyncEnumerable<T> |
| ✅ | IQueryable | Query<T>(...) is an IQueryable<T> with Where/OrderBy/Skip/Take/Select/Include+ThenInclude (relationship-inferred eager load) and async terminals (ToListAsync/CountAsync/FirstAsync/…); results are async-only |
| ✅ | Async‑first | ValueTask<T> API, OpenAsync(...), await using (IAsyncDisposable), IProgress<T> callbacks |
| ✅ | Stream‑based I/O | Open from any seekable Stream (files, byte arrays, blobs, embedded resources) |
| ✅ | Encryption | Read/update built-in JET RC4 and ACE CryptoAPI, Standard AES and Agile AES; create JET RC4 or ACE Agile AES and maintain passwords. See Encryption Support for provider and platform scope. |
| Partial | Schema features | Supported indexes, primary & foreign keys with referential integrity (cascade update/delete), and linked tables; complete native schema and application dependency preservation remains incomplete |
| Partial | Complex columns | Read and add attachments and multi-value items (ACCDB); individual replacement/removal and append-only history writes remain incomplete |
| Partial | Calculated columns | ACCDB metadata, cached values, and a bounded row-local evaluator for a supported subset of Access expressions |
| ✅ | Concurrency | .ldb / .laccdb lockfile + BCL page-level byte-range locks where supported |
| ✅ | Transactions | Group or nest changes with CommitAsync / RollbackAsync; file transactions recover after process crashes (see guarantees) |
| ✅ | Storage maintenance | Access-style free-page reuse, free-page scrubbing, opt-in secure erase, and tail shrinking |
| ✅ | Performance | Configurable LRU page cache, default parallel read-ahead for eligible page scans, streams millions of rows without loading the file |
The parser enforces bounds on several page, long-value, attachment and encryption paths. Hostile-file auditing and fuzz coverage remain ongoing; the CVE review is a threat-model inventory, not proof that every attack surface is mitigated. See the security work and the vulnerability review.
Access/text linked data requires a LinkedSourceResolver that supplies an authorized stream. The library reads that exact stream without opening catalog paths or filename-derived sidecars; see linked-source configuration.
The test suite draws from and extends the coverage of Jackcess, mdbtools, OpenMcdf, and Microsoft's Extensible Storage Engine for analogous storage-engine risk categories, with additional coverage for corner cases, corruption resilience, and format variants.
Access compatibility is checked with Microsoft-authored fixtures and DAO tests. Coverage is incomplete; see the writer disk-format validation matrix for tested formats and native-engine results, and the open requirements for remaining work.
Beyond functional tests, the codebase is validated by:
- Strict compiler settings — nullable reference types, warnings-as-errors,
WarningLevel 9999,AnalysisLevel latest-all, and arithmetic overflow checking enabled globally - Static analysis — Roslyn .NET analyzers, Roslynator, StyleCop, and the
.editorconfigcode-style rules, all errors in the Release build of the library and the tests that CI runs on every push to main and every pull request - Continuous integration — GitHub Actions builds the solution in Release and runs the test suite on .NET 10 and on .NET 8 (the
netstandard2.1build) on Windows, on every push to main and every pull request; a release tag runs the same checks and publishes the package that run built only after it passes - Reproducible builds — deterministic compilation via DotNet.ReproducibleBuilds; reproduction requires matching locked dependencies, SDK, version and build inputs
- Access Compact & Repair checks — DAO-guarded tests exercise tables, indexes, relationships and complex values. The validation matrix records the tested revision, native-engine results and unresolved gaps.
- Index key fixture parity — long text/MEMO index keys with embedded line breaks are validated against Access-authored fixtures: Jet4 (V2000 / V2003 / V2007) is byte-exact, and V2010 ACE is byte-exact for the checked-in Access-authored
Table11/Table11_desclong rows. The V2010 encoder also includes the DAO-derived 65-character contribution tables for the plain, auxiliary, row10, row11, and row12 long-row suffix contexts, with probe validation showing zero mismatches across the exported matrices and observed double-space sweeps. See GeneralLegacyEncoderFixtureTests.cs, GeneralEncoderFixtureTests.cs. - Fuzz testing — an explicit hosted workflow runs deterministic corpus mutations and truncations with per-process time and memory limits; reproductions and logs are retained on failure
- Memory safety analysis — control-flow and resource-leak detection via InferSharp
dotnet add package JetDatabaseWriterInstall-Package JetDatabaseWriterThe package targets .NET 10 and .NET Standard 2.1. Applications on .NET Core 3.x and .NET 5–9 use the .NET Standard build; .NET Framework is not supported. See runtime and NativeAOT guidance for details.
Open an existing database, list its tables, and read rows into a small C# model. This example expects an Orders table; change the path, table name and properties to match your file.
using JetDatabaseWriter;
await using var reader = await AccessReader.OpenAsync("database.mdb");
var tables = await reader.ListTablesAsync();
Console.WriteLine($"Tables: {string.Join(", ", tables)}");
var orders = await reader.ReadTableAsync<Order>("Orders", maxRows: 100);
foreach (var order in orders)
Console.WriteLine($"#{order.OrderID}: {order.Freight:C}");
public class Order
{
public int OrderID { get; set; }
public decimal Freight { get; set; }
}Properties match column names without regard to case. For large tables, stream rows with await foreach (var order in reader.Rows<Order>("Orders")) instead of loading a list. The reading guide covers filtering, indexes, relationships, attachments and other column types.
Create a new database and insert a row using column names. The destination file must not already exist.
using JetDatabaseWriter;
using JetDatabaseWriter.Enums;
using JetDatabaseWriter.Models;
await using var writer = await AccessWriter.CreateDatabaseAsync(
"contacts.mdb", DatabaseFormat.Jet4Mdb);
await writer.CreateTableAsync("Contacts", new[]
{
new ColumnDefinition("Id", typeof(int)) { IsPrimaryKey = true },
new ColumnDefinition("Name", typeof(string), maxLength: 100),
});
await writer.InsertRowAsync("Contacts", new RowValues
{
["Id"] = 1,
["Name"] = "Alice",
});To edit an existing file, open it with AccessWriter.OpenAsync("contacts.mdb"). Use one writer per file and dispose it when finished; await using does this automatically. The writing guide covers updates, deletes, schema changes and constraints, and the operations guide explains transactions.
| I want to… | Guide |
|---|---|
| Read, filter or stream data | Reading and querying |
| Insert or edit data and change schemas | Writing data |
| Use streams, configure options or handle failures | Database operations |
| Generate C# models from an existing database | Scaffolding |
| Understand Access terminology and file formats | Glossary and design notes |
Pass a password when opening a protected database:
using JetDatabaseWriter;
await using var reader = await AccessReader.OpenAsync(
"protected.accdb", new AccessReaderOptions("your-password"));Writers accept the password through AccessWriterOptions.
| Format | Current support |
|---|---|
| Jet4 password protection and RC4 encryption | Read, update, create, encrypt/decrypt and change passwords, including native security identity remasking and rebuilding indexed Owner/SID fields. |
| ACE Agile encryption | Read and update AES-128/192/256 with CBC or CFB8 and SHA-1/256/384/512; create and maintain using AES-256-CBC/SHA-512. Microsoft-compacted fixtures cover AES-192, CFB8, SHA-256 and SHA-384 as well as the default algorithms, including distinct password and page parameters. |
| Jet3 RC4 encryption and password protection | Native code-page passwords, page updates and maintenance; DAO 3.6 fixtures and read/write/compact checks cover Access 97. |
| ACE Standard and RC4 CryptoAPI providers | Read and update native pages, including 50,000-iteration Standard and compatibility AES. Microsoft DAO read/write/compact checks and a native Standard output fixture verify interoperability. |
AccessWriter.CreateDatabaseAsync encrypts a new database when AccessWriterOptions.Password is nonempty, before writing the initial database image. Use AccessDatabaseEncryption.EncryptAsync, DecryptAsync and ChangePasswordAsync for file maintenance. These fully managed operations stream pages into an adjacent temporary file and replace the original after successful completion. Use a containing directory protected from untrusted access; see maintenance permissions and durability. Ordinary updates preserve the original encryption provider and key. Password maintenance preserves JET password-only mode; ACE password changes use fresh AES-256-CBC/SHA-512 encryption.
The support status covers the built-in providers listed above. Third-party extensible providers, non-AES Agile ciphers and Office encrypted compound packages are not supported database inputs. Custom workgroup identities are preserved; the library does not authenticate workgroup users. See the native encryption evidence for fixture provenance and the encryption options for resource limits, replacement recovery and platform durability limits.
- Keep the file stable while reading, and use one writer per file. A reader caches data and does not refresh it or provide a consistent snapshot of a changing file. Reopen it after changes. Lockfiles help coordinate access but do not exclude every external writer on every platform.
- Crash recovery requires the database and its journal. File transactions and physical shrinking recover interrupted writes on writer reopen; keep the adjacent
.jdw-journalwith the database until recovery finishes. Custom streams and power loss have narrower guarantees. See transaction guarantees. - Access feature coverage varies. Third-party encryption providers, some index formats and some expressions are unsupported. For example, a default or validation rule using
DLookUprefuses the write. See the compatibility evidence and detailed limits. - Access size and encoding constraints still apply. Tables are limited to 255 columns; rows must fit the supported page layout. Jet3 text must fit the database's code page. Large MEMO and binary OLE values can use separate pages; see size limits.
- Storage maintenance is limited.
ShrinkDatabaseAsyncremoves free pages at the end of a file; it does not perform a full Access Compact & Repair. Deleted data is not securely erased by default; see storage maintenance. - This is a data library. It supports LINQ queries, but does not execute SQL, saved Access queries, forms, reports, macros or VBA, and does not provide an ODBC driver. Complete preservation of Access application objects during edits is not established.
JetDatabaseWriter reads and writes the database's pages directly: the catalog identifies tables, table definitions describe columns and indexes, and data pages hold rows. Large values use separate page chains. See the library structure and format notes for the details.
Issues and pull requests are welcome. Please open an issue to discuss larger changes before submitting a PR.
Local builds and targeted tests are welcome, for example:
dotnet build JetDatabaseWriter.slnx -c Release
dotnet test --project JetDatabaseWriter.Tests -c Release -f net10.0 --no-build --filter-class "JetDatabaseWriter.Tests.Indexes.IndexWriterTests"Full test suites run only on CI, including a full run of one target framework. All benchmarks also run only on CI, even single-case, short, or dry runs. Local builds and selected tests do not replace the required CI gate. See AGENTS.md for the execution policy and hosted workflow commands.
For hosted compiler and analyzer feedback on an unpublished local change, stage newly created files, then run:
pwsh -NoProfile -File scripts/submit-candidate.ps1 -Base origin/mainThe candidate workflow runs from main, applies the patch against its published base, installs the candidate SDK and checks every solution target. Its candidate-results artifact contains logs, formatter reports, format-fixes.patch and the complete tested candidate.patch. Review formatter fixes before applying them. Workflow input limits restrict patch size; the helper refuses oversized or incomplete submissions. The final integrated main revision still requires ordinary CI.
SDK changes update the exact pin and all dependency locks together, including the separate seed, trimmed and NativeAOT consumer configurations:
pwsh -NoProfile -File scripts/update-sdk.ps1 -Version 10.0.402
# Or receive a hosted patch without installing or building locally:
gh workflow run sdk-lock-refresh.yml --ref main -f sdk-version=10.0.402The local updater performs restores only and rolls back file changes if verification fails. The hosted workflow returns sdk-lock-patch, with the patch's base revision and restore verdict. Review and commit the SDK pin and changed locks, then run the ordinary main gate.
CI retains normalized golden specimens in golden-bytes-net10.0 and golden-bytes-net8.0. Failure reports identify every changed byte's offset and page, including added or removed bytes. The approved binaries and pinned hashes are reviewed together for deliberate format changes; Microsoft-authored fixtures and DAO checks provide separate compatibility evidence. Benchmark comparisons require successful exact CI for both head and published baseline, and shared fixture correctness also runs in ordinary tests.
Releases are published from version tags; PUBLISH.md describes the steps.
MIT — see LICENSE for details.