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, no StringBuilder, 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.SequentialAccess and 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 INQxxx diagnostic 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.