Back to the README · Reading guide
Run these commands from a checkout of this repository with the .NET SDK pinned by global.json. The generated models can then be used in your application.
The JetDatabaseWriter.Scaffold CLI tool reads the schema of every user table in a JET database and emits one C# entity-model source file per table.
# Positional argument
dotnet run --project src/JetDatabaseWriter.Scaffold -- Northwind.mdb
# Named options
dotnet run --project src/JetDatabaseWriter.Scaffold -- --database Northwind.mdb --output ./Entities --namespace MyApp.Models
# Emit records with nullable reference types
dotnet run --project src/JetDatabaseWriter.Scaffold -- Northwind.mdb --records --nullable
# Password-protected database
dotnet run --project src/JetDatabaseWriter.Scaffold -- Secure.accdb --password secret
| Option | Short | Default | Description |
|---|---|---|---|
--database |
-d |
(positional) | Path to the .mdb or .accdb file |
--output |
-o |
./Models |
Output directory for generated files |
--namespace |
-n |
GeneratedModels |
Namespace for generated classes |
--password |
-p |
— | Database password (for encrypted files) |
--records |
false |
Emit C# record types instead of class |
|
--nullable |
true |
Emit nullable reference types (#nullable enable) |
Given an Orders table with columns OrderID (int), OrderDate (DateTime), and Freight (decimal), the tool generates:
// <auto-generated />
#nullable enable
using System;
namespace GeneratedModels;
public sealed class Orders
{
/// <summary>Column: OrderID (Long Integer, 4 bytes).</summary>
public int OrderID { get; set; }
/// <summary>Column: OrderDate (DateTime, 8 bytes).</summary>
public DateTime OrderDate { get; set; }
/// <summary>Column: Freight (Currency, 8 bytes).</summary>
public decimal Freight { get; set; }
}
Table and column names are converted to PascalCase C# identifiers: spaces, hyphens and other special characters are dropped, a leading digit gets a _ prefix, and a name with no letters or digits becomes Unknown. A property named like its class or like a member every object or record has (ToString, Equals, GetHashCode, GetType, MemberwiseClone, ReferenceEquals, EqualityContract, PrintMembers, Clone) gets a Value suffix (ToStringValue), a navigation named that way a Navigation suffix, and a name another property already has a numeric suffix (Name2). Framework types, mapping attributes and navigation types use globally qualified references, so namespaces such as MyApp.System and MyApp.DateTime work. When the identifier differs from the Access name by more than case, the generated code carries [Table("Order Details")] on the class or [Column("Unit Price")] on the property, so the properties read, write and query the original columns, and Include navigations to the entity resolve the original table.
Every table gets its own class and file. A table whose cleaned name is its own name, ignoring case, claims that name first. Reserved object/record member names receive an Entity suffix. An exact generated identity that would capture a required type or namespace uses the same suffix policy; for example, a DateTime entity in namespace System becomes DateTimeEntity when the models need the framework’s System.DateTime. Names already claimed by another table, ignoring case, receive a numeric suffix: OrderDetails and Order Details become OrderDetails and OrderDetails2. Navigations are emitted only between generated tables. An invalid C# namespace, or a namespace whose complete identity replaces a required type (such as System.DateTime), stops the tool with exit code 1 before it creates output.
When the database declares foreign-key relationships, each generated entity also gets navigation properties inferred from MSysRelationships: a reference to the parent (named after the foreign-key column, EF-style — CustomerID → Customer) and a collection of children (named after the child table). These pair directly with reader.Query<T>(...).Include(...) (with using JetDatabaseWriter.Linq;):
public sealed class Customers
{
public int CustomerID { get; set; }
/// <summary>Navigation: related Orders children.</summary>
public ICollection<Orders> Orders { get; set; } = new List<Orders>();
}
public sealed class Orders
{
public int OrderID { get; set; }
public int CustomerID { get; set; }
/// <summary>Navigation: the related Customers (parent).</summary>
public Customers? Customer { get; set; }
}