/DB

NativeAOT

Publish an application using this library with PublishAot. What is already AOT-safe, the one API shape you have to swap, and the single dependency that currently rules AOT out.

updated 2 Sept 20264 min readv0.3.8View as Markdown

Overview

The library is built for NativeAOT: the source generator emits typed materialisation and command building at compile time, so there is no runtime reflection or IL emit in the hot path. An application referencing Socigy.OpenSource.DB publishes with PublishAot=true and runs at the same speed it does under the JIT — see Benchmarks.

dotnet publish -c Release -p:PublishAot=true

There are exactly two things to know before you do: one API shape in your own code that has to change, and one optional package that currently cannot be published this way.


Use the `string[]` overloads for column selectors

Every API that takes a column selector has two forms:

row.Update().WithFields(x => new object?[] { x.Name, x.Age })   // expression selector
row.Update().WithFields(nameof(Row.Name), nameof(Row.Age))      // string selector

They produce identical SQL. The expression form is fine under the JIT, but the C# compiler lowers new object?[] { ... } inside an expression tree to Expression.NewArrayInit, which is annotated [RequiresDynamicCode]. Under NativeAOT that produces an IL3050 at your call site, and with TreatWarningsAsErrors the publish produces no binary at all.

WARNING
The warning is attributed to your assembly, not to this library, and it does not appear during dotnet build — only when ILC runs at publish time. A project can build perfectly cleanly and still fail to publish.

Swap the selector; leave everything else alone.

Expression form AOT-safe form
InsertAsync(row, keep: r => new object?[] { r.Id }) InsertAsync(row, [nameof(Row.Id)])
InsertMultipleAsync(rows, conn, fields, keep: …) InsertMultipleAsync(rows, conn, [nameof(Row.Id)], …)
InsertMultipleCopyAsync(rows, conn, fields, keep: …) InsertMultipleCopyAsync(rows, conn, [nameof(Row.Id)], …)
.Select(x => new object?[] { x.Id, x.Name }) .Select(nameof(Row.Id), nameof(Row.Name))
.OrderBy(x => new object?[] { x.CreatedAt }) .OrderBy(nameof(Row.CreatedAt))
.OrderByDesc(x => new object?[] { x.CreatedAt }) .OrderByDesc(nameof(Row.CreatedAt))
.WithFields(x => new object?[] { x.Name }) .WithFields(nameof(Row.Name))
.ExceptFields(x => new object?[] { x.CreatedAt }) .ExceptFields(nameof(Row.CreatedAt))
.ExcludeAutoFields(x => new object?[] { x.Id }) .ExcludeAutoFields(nameof(Row.Id))

The string overloads accept either the C# property name (nameof(Row.CreatedAt), recommended — the compiler checks it) or the database column name ("created_at").

Predicates are unaffected

Only selectors are a problem. A WHERE predicate is an Expression<Func<T, bool>> with no array construction in it, and is translated to SQL without runtime code generation:

// Both fine under NativeAOT — no change needed.
await foreach (var u in User.Query(x => x.Age >= 18 && x.TenantId == tenant)
                   .OrderBy(nameof(User.CreatedAt))
                   .WithConnection(conn)
                   .ExecuteAsync()) { }

await row.Update().WithFields(nameof(User.Email)).Where(x => x.Id == id).WithConnection(conn).ExecuteAsync();

Collection predicates (ids.Contains(x.Id), which becomes = ANY(@p)) are AOT-safe too.


Finding your remaining call sites

IL3050 names the containing method rather than the line, and the default publish output collapses every warning into a single IL3053. Turn that off to get one message per site:

dotnet publish -c Release -p:PublishAot=true -p:TrimmerSingleWarn=false

For a large codebase, a plain text search is faster than a publish cycle. The expression form always contains new object?[] (or new object[]) inside a lambda:

grep -rn "new object?\[\]" --include=*.cs src/

The HashiCorp Vault package rules out NativeAOT

ERROR
Referencing Socigy.OpenSource.DB.HashiCorp currently prevents a NativeAOT publish. This is not something an application can work around.

The package depends on VaultSharp, which serialises its request and response models with reflection-based System.Text.Json. That produces IL2026 warnings from inside VaultSharp itself (VaultSharp.Core.Polymath.MakeRequestAsync and VaultApiException), and:

  • <TrimmerRootAssembly Include="VaultSharp" /> makes the reflection safe but does not silence IL2026.
  • Suppressing IL2026 project-wide would suppress it for your own code too, which defeats the point of running the analysis at all.

Under TreatWarningsAsErrors the publish fails and leaves an empty output directory.

The call sites are inside VaultSharp, so nothing in this library or in your application can fix them. If you need both Vault-managed encryption or credentials and NativeAOT today, run the Vault integration in a separate, JIT-compiled process — a small sidecar that holds the Vault client and hands the AOT-published application what it needs.

Everything else in the library, including field encryption with the built-in AES encryptor, publishes cleanly.


How this is verified

The library does not take AOT-safety on trust. Three gates run on every build:

  1. Analyser gate. The Core sources are compiled a second time under a modern target framework with the trim and AOT analysers enabled and every IL2xxx / IL3xxx promoted to an error. Core itself targets netstandard2.0, where those analysers cannot run at all, so without this second compilation no trim warning in the library would ever be visible before it reached an application.
  2. Generated-code gate. A project exercising the generated query, insert, update, and procedure APIs is built with IsAotCompatible, so the emitted code is analysed the way an application's would be.
  3. Publish gate. A sample application that uses only the string[] overloads is published with PublishAot=true, running the real ILC compiler, and must produce a native binary with zero IL warnings.

The third exists because the first two are not sufficient on their own: the Roslyn analysers never flag expression-tree construction, so a project can build with zero warnings and still fail to publish. That is exactly the gap this page's first section is about.