Inquiry
A compile-time SQL micro-ORM for .NET. You declare attributed entities and partial store classes; a Roslyn incremental source generator emits the matching partials with real C# bodies, materializers, and dependency-injection wiring. The runtime ships zero SQL — every statement is baked at build time as a const string.
// You write this:
[InquiryTable("Shippers")]
public sealed class Shipper
{
[InquiryKey("ShipperID", IsGenerated = true)]
public int? ShipperID { get; set; }
[InquiryColumn] public string CompanyName { get; set; } = "";
[InquiryColumn] public string? Phone { get; set; }
}
public partial class ShipperStore : InquiryStore<Shipper>
{
[InquirySelectAll]
public partial Task<IReadOnlyList<Shipper>> SelectAllAsync(CancellationToken ct = default);
[InquirySelectOneByKey]
public partial Task<Shipper?> SelectByKeyAsync(int? id, CancellationToken ct = default);
[InquiryInsert]
public partial Task<int> InsertAsync(Shipper shipper, CancellationToken ct = default);
}
The Roslyn generator produces, at build time:
// Generated by Inquiry.Sqlite.Analyzer
partial class ShipperStore
{
private const string _sqlSelectAll = "SELECT \"ShipperID\", \"CompanyName\", \"Phone\" FROM \"Shippers\"";
private const string _sqlSelectByKey = "SELECT \"ShipperID\", \"CompanyName\", \"Phone\" FROM \"Shippers\" WHERE \"ShipperID\" = @ShipperID";
private const string _sqlInsert = "INSERT INTO \"Shippers\" (\"CompanyName\", \"Phone\") VALUES (@CompanyName, @Phone)";
// ...plus type-safe partial method bodies that call the pipeline with these consts.
}
Why Inquiry
- Zero runtime SQL building. The query strings are
const strings in your assembly — no interpolation, noStringBuilder, no LINQ provider, no expression trees at runtime. The provider is told exactly what to execute. - Apples-to-apples performance with raw ADO.NET. Generated readers use
CommandBehavior.SequentialAccessand per-call struct materializers — on buffered CRUD workloads (select-all, point reads, insert, update, upsert), measured allocation matches hand-written ADO.NET. See the benchmark suite for workload-scoped evidence and methodology. - Six real databases, identical surface. Sqlite, SQL Server, PostgreSQL, MySQL, MariaDB, Oracle — same attributes, same store API. The per-dialect generator picks the right SQL flavor at compile time.
- Schema you can deploy. The generator also emits
InquiryGeneratedSchema.Ddl— your CREATE TABLE statements, in dependency order, per dialect. Use it for tests, migrations, or first-run bootstrap. - Diagnostics at compile time, not at 3 AM. Bad column name? Missing key? Unsupported return shape? You get an
INQxxxdiagnostic at build, with a source location.
Get started
- 5-minute walkthrough — entity → store → first query.
- How it works — the compile-time pipeline explained.
- Features — CRUD, pagination, soft delete, FTS, batch, and more.
- Providers — per-dialect notes for the 6 supported engines.
- API reference — auto-generated from XML doc comments.
- Develop — project status, roadmap, and contributor docs.