# 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.

## 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](/database/0.3.8/performance/benchmarks#nativeaot-the-one-nobody-else-can-match).

```bash
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:

```csharp
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:

```csharp
// 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:

```bash
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:

```bash
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.
