# Multiplatform AOT met SQLite: Hoe het aan de slag te krijgen!

<!--category -- .NET, AOT, SQLite, DevOps -->
<datetime class="hidden">2024-12-12T15:00</datetime>

Als je hebt geprobeerd om te verzenden .NET toepassingen als single, native executables met behulp van Native AOT, je hebt waarschijnlijk lopen face-first in de SQLite muur. U krijgt alles geconfigureerd, de bouw voltooid met succes, en dan [...] boom runtime crashes met cryptische `DllNotFoundException` fouten over `e_sqlite3`. Laat me je de uren van frustratie die ik heb meegemaakt besparen en je precies laten zien hoe je SQLite kunt laten werken met Native AOT in Windows, Linux (inclusief ARM64 Raspberry Pi), en macOS.

## Wat is AOT? (En wat kan jou dat schelen?)

Laten we beginnen met de absolute basics. Als je ooit een .NET applicatie hebt gebouwd en je afvraagt waarom je de ".NET Runtime" op servers moet installeren of waarom je console app een seconde nodig heeft om de eerste keer dat je het uitvoert te starten, is AOT het antwoord op deze problemen.

### Hoe werkt .NET normaal gesproken (JIT Compilatie)

Wanneer je C# code schrijft en je applicatie bouwt, produceert de compiler geen machinecode die je CPU direct kan uitvoeren. In plaats daarvan produceert het iets genaamd **Tussentaal (IL)**Denk erover na als halverwege tussen uw C# code en de werkelijke machine instructies.

Als je je .NET app draait, gebeurt dit:

1. Uw computer laadt de .NET Runtime (een apart programma)
2. De runtime leest je IL-code
3. **Just-in-time (JIT)** compiler converteert IL naar native machine code als uw app draait
4. Uw CPU voert eindelijk die machinecode uit

Dit is als het hebben van een vertaler die uw recept (IL) leest en het verbaal vertaalt naar een chef (CPU) regel voor regel tijdens het koken. Het werkt, maar er is overhead:

- De .NET Runtime is 50-150MB aan extra bestanden die u nodig hebt om te implementeren
- De JIT compiler heeft tijd nodig om code te vertalen (waarom uw app de eerste keer traag is)
- De JIT compiler zelf zit in het geheugen terwijl uw app draait

### Voer AOT (Ahead-Of-Time Compilation) in

Native AOT draait dit model op zijn kop. In plaats van het vertalen van uw code op runtime, het vertaalt alles **bij bouwtijd**. Je eindigt met een enkel uitvoerbaar bestand dat de werkelijke machinecode bevat die je CPU direct kan draaien geen runtime, geen vertaler, geen wachten.

Denk aan het als het krijgen van een professioneel vertaald recept boek in plaats van het huren van een live vertaler. Het werk is gedaan een keer, vooraf, en het resultaat is klaar om onmiddellijk te gebruiken.

### De Game-Changing Voordelen

Dit is wat Native AOT je geeft:

**1. Kleine uitvoerbare bestanden**: 10-30MB in plaats van 150MB+

Uw app en alles wat het nodig heeft wordt gecompileerd in een kleine binaire. Geen aparte runtime bestanden.

**2. Instant opstarten**: 80% snellere koude start

Mijn tests: normaal .NET nam ~800ms om te beginnen, AOT nam ~150ms. Er is geen JIT warm-up tijd de machine code is klaar om onmiddellijk uit te voeren.

**3. Nul afhankelijkheden**: Geen .NET runtime vereist

U kunt uw uitvoerbaar bestand kopiëren naar elke machine met de juiste OS (Windows/Linux/Mac) en het draait gewoon. Geen "install .NET 9 Runtime" voorwaarde.

**4. Lager geheugengebruik**: Ongeveer 50% minder geheugen

Geen JIT compiler zitten in het geheugen. In mijn gateway applicatie, normaal .NET gebruikt 85MB inactief, AOT gebruikt 42MB.

**5. Beter voor beperkte omgevingen**: Works where JIT can't

Sommige omgevingen (bepaalde Docker containers, iOS, embedded systemen) staan geen runtime code generatie toe. AOT werkt overal.

### The Trade-offs (There's Always a Catch)

AOT is geen magie.

**1. Geen dynamische codegeneratie**

Alles wat code genereert op runtime zal niet werken:

- `System.Reflection.Emit` (dynamisch creëren van typen)
- Dynamische montagebelasting (laden van DLL's op runtime)
- Sommige mooie reflectie trucs

De meeste normale .NET code is prima, maar sommige kaders die sterk afhankelijk zijn van reflectie hebben speciale configuratie nodig.

**2. Platform-specifieke builds**

JIT compilatie produceert IL die draait op elk platform. AOT produceert native machine code voor **één specifiek platform**. U dient apart te bouwen voor:

- Windows x64
- Linux x64
- Linux ARM64 (Raspberry Pi)
- macOS Intel
- macOS Apple Silicium

We zullen deze automatiseren met GitHub Acties later.

**3. Langere bouwtijden**

In plaats van te compileren naar IL in seconden, compileert AOT helemaal naar machinecode. Verwacht 2-5 minuten in plaats van 10 seconden. Het is een eenmalige kosten voor permanente voordelen.

**4. Sommige functies hebben extra configuratie nodig**

JSON serialisatie, Entity Framework, en alles wat gebruik maakt van zware reflectie kan nodig hebben om de compiler expliciet te vertellen welke types te houden. We zullen dit behandelen.

### Wanneer moet je AOT gebruiken?

Native AOT is perfect voor:

- **CLI-gereedschappen**: Command-line utilities waar direct opstarten belangrijk is
- **Microdiensten**: Kleinere Docker afbeeldingen, snellere schaalvergroting in Kubernetes
- **Serverless/Lambda**: Koude starttijd heeft direct invloed op uw rekening
- **Randapparatuur**: Raspberry Pi, IoT apparaten met beperkte middelen
- **Gateway-toepassingen**: Reverse proxies, API gateways met hoge doorvoer
- **Bureaubladgereedschappen**: Ship a single .exe with no "install .NET first" step

Skip AOT for:

- Traditionele webapps waar opstarttijd niet uitmaakt
- Apps die gebruik maken van zwaar entiteitskader met veel migraties
- Plugin-systemen die DLL's dynamisch laden
- Alles wat code genereert op runtime

Voor CLI-tools, microservices, containers en randapparatuur zijn de verbeteringen game-changing. Maar er is een vangst wanneer u databases toevoegen specifiek SQLite.

## Het SQLite-probleem

Nu je begrijpt wat AOT is, laten we het hebben over het enige grootste pijnpunt: SQLite. Dit is de muur die de meeste mensen raken bij het proberen om AOT te gebruiken met database-backed applicaties.

### Wat gebeurt er (The Frustrating Experience)

Hier is de typische reis:

1. U voegt toe `Microsoft.Data.Sqlite` naar uw project
2. U configureert `PublishAot=true` in uw `.csproj`
3. De bouw is compleet zonder fouten.Alles ziet er goed uit!
4. U draait het uitvoerbare bestand en onmiddellijk krijgen:

```
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies
```

Je app crasht voordat het iets kan doen.

### Inzicht in inheemse bibliotheken (A Quick Detour)

Om het probleem te begrijpen, moet je weten over **native libraries**.

SQLite is niet geschreven in .NET. Het is geschreven in C. Het is gecompileerd naar platform-specifieke native code:

- `e_sqlite3.dll` op Windows
- `libe_sqlite3.so` op Linux
- `libe_sqlite3.dylib` op macOS

Wanneer u dit middel gebruikt `Microsoft.Data.Sqlite` in normale .NET, het is gewoon een wrapper rond deze native SQLite bibliotheek. Op runtime, het probeert **dynamisch laden** het juiste native bestand voor uw platform.

Dit werkt prima bij normaal .NET omdat:

1. De runtime kan bibliotheken dynamisch laden
2. NuGet pakketten kunnen native bibliotheken voor alle platforms omvatten
3. De juiste wordt geselecteerd op runtime

### Waarom AOT dit breekt

Inheemse AOT heeft twee kenmerken die botsen met de aanpak van SQLite:

**1. Agressief trimmen**: AOT verwijdert elke code die het denkt dat u niet gebruikt. Als het niet statisch kan bewijzen dat u iets nodig hebt, wordt het verwijderd. Dynamische bibliotheek laden verward de trimmer het kan de verbinding tussen uw code en de native SQLite DLL niet zien.

**2. Geen dynamische laadondersteuning**: AOT produceert een op zichzelf staand binair. Het verwacht dat alle native afhankelijkheden expliciet gekoppeld worden op compilatietijd, niet dynamisch geladen op runtime.

Het resultaat: `Microsoft.Data.Sqlite` verwacht een native SQLite-bibliotheek te vinden op runtime, maar AOT heeft het ofwel weggeknipt of weet niet hoe het goed te bundelen.

### De Transitive Dependency Nightmare

Nog erger, als je andere NuGet pakketten die gebruik maken van SQLite (zoals sommige ORM bibliotheken of mijn `mostlylucid.ephemeral.complete` pakket), kunt u eindigen met **meerdere incompatibele SQLite providers** in je afhankelijkheidsboom.

Elke provider probeert anders te werken:

- Men zou kunnen verwachten OS-geleverd SQLite (alleen Windows)
- Een ander zou zijn eigen SQLite kunnen bundelen
- Een andere zou een andere native library versie kunnen gebruiken

De AOT compiler raakt in de war over welke te gebruiken, en vaak het resultaat is dat het geen van hen of erger omvat, bevat conflicterende bestanden die niet samen kunnen werken.

## De oplossing: Gebruik de bundel

Na uren van frustratie, vond ik de oplossing: `SQLitePCLRaw.bundle_e_sqlite3`. Dit is een speciaal NuGet pakket speciaal ontworpen om te werken met AOT.

Voeg deze twee pakketten toe aan uw project:

```xml
<ItemGroup>
  <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
  <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
</ItemGroup>
```

### Wat doet de Bundle?

De `bundle_e_sqlite3` pakket is anders dan normale SQLite providers:

**1. Het omvat voorgecompileerde native SQLite bibliotheken voor elk groot platform:**

- Windows (x64, x86, ARM64)
- Linux (x64, ARM64, op moslims gebaseerde distro's zoals Alpine)
- macOS (x64 Intel, ARM64 Apple Silicium)

**2. Deze native bibliotheken zijn verpakt op een manier die de AOT compiler begrijpt**

De bundel markeert de oorspronkelijke afhankelijkheden expliciet zodat de AOT compiler weet om ze in de uiteindelijke binaire op te nemen. Geen dynamisch laden, geen runtime zoeken naar bestanden.

**3. Het is ontworpen voor cross-platform builds**

Een pakket werkt voor alle platforms. Je hebt geen platformspecifieke pakketten of voorwaardelijke referenties nodig.

### Waarom dit werkt

Herinner je je de twee problemen die we ontdekten?

**Probleem 1: AOT's trimmen verwijdert bibliotheken die het niet kan zien worden gebruikt**

- Oplossing: De bundel verklaart expliciet dat haar native libraries as build assets die moeten worden opgenomen

**Probleem 2: AOT ondersteunt dynamisch laden niet**

- Oplossing: De bundel verbindt de SQLite-bibliotheken op compilatietijd statisch

Het resultaat: wanneer u voor Windows x64 bouwt, bevat de bundel `e_sqlite3.dll`. Wanneer je bouwt voor Linux ARM64, bevat het de ARM64 `libe_sqlite3.so`Alles werkt gewoon.

### Kritiek: Initialiseer de bundel (Skip dit niet!)

Hier is het deel dat 90% van de mensen die proberen om SQLite te gebruiken met AOT struikelt, inclusief mij bij mijn eerste poging.

**Met normaal .NET**, de SQLite bundel initialiseert zichzelf automatisch de eerste keer dat u SQLite gebruikt. Magie gebeurt achter de schermen te doen je hoeft niets te doen.

**Met inheemse AOT**, deze automatische initialisatie werkt niet. De AOT compiler kan de automatische opstartcode niet zien (het ziet eruit als ongebruikte code en wordt getrimd), zodat u **moet handmatig initialiseren** de bundel bij het begin van uw toepassing.

Hier is de magische lijn die je nodig hebt:

```csharp
using SQLitePCL;

public class Program
{
    public static void Main(string[] args)
    {
        // THIS IS CRITICAL: Initialize SQLite FIRST, before ANYTHING else
        SQLitePCL.Batteries.Init();

        // Now you can do normal application setup
        var builder = WebApplication.CreateBuilder(args);

        // This is now safe - SQLite is initialized
        builder.Services.AddDbContext<MyDbContext>(options =>
            options.UseSqlite("Data Source=app.db"));

        var app = builder.Build();
        app.Run();
    }
}
```

### Wat doet `Batteries.Init()` Doen?

Deze methode vertelt de SQLite bundel om:

1. Zoek de juiste native SQLite-bibliotheek voor uw huidige platform
2. Laad het in het geheugen
3. Verbind alle verbindingen tussen `Microsoft.Data.Sqlite` en de moedertaalbibliotheek

Het heet "Batteries" omdat het de "Batteries Included" bundel is alles wat je nodig hebt is verpakt samen.

### Waar moet ik het plaatsen?

Zet `Batteries.Init()` als **de eerste regel** in uw `Main` methode, vóór:

- De applicatiebouwer aanmaken
- Het configureren van afhankelijkheidsinjectie
- Openen van databaseverbindingen
- Het lezen van configuratiebestanden die SQLite zouden kunnen gebruiken

Denk aan het als het aansluiten van een apparaat voordat u probeert om het aan te zetten. Als u probeert om SQLite te gebruiken voordat u belt `Init()`, je krijgt nog steeds de `DllNotFoundException` ook al is de DLL correct gebundeld in uw toepassing.

### Wat gebeurt er als je het vergeet?

Bent u vergeten te bellen? `Batteries.Init()`, uw app zal:

1. Bouw succesvol (de compiler waarschuwt u niet)
2. Starten
3. Crash het moment dat het SQLite probeert te gebruiken met:

```
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies
```

Dit is verwarrend omdat de DLL **is** gebundeld in uw toepassing het is gewoon niet geïnitialiseerd. Ik verloor twee uur aan deze fout. Wees niet zoals ik.

## Projectconfiguratie voltooien

Hier is een volle `.csproj` geconfigureerd voor multi-platform Native AOT met SQLite:

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <!-- Native AOT -->
    <PublishAot>true</PublishAot>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
    <InvariantGlobalization>true</InvariantGlobalization>

    <!-- Single File -->
    <PublishSingleFile>true</PublishSingleFile>
    <StripSymbols>true</StripSymbols>

    <!-- Optimization -->
    <OptimizationPreference>Speed</OptimizationPreference>

    <!-- Multi-platform targets -->
    <RuntimeIdentifiers>
      win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64
    </RuntimeIdentifiers>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
  </ItemGroup>

</Project>
```

### Sleutelinstellingen uitgelegd (In gewoon Engels)

Laat me opsplitsen wat elk van deze instellingen doet:

**`PublishAot=true`**

Dit is de master switch die Native AOT compilatie mogelijk maakt. Zonder dit, krijg je normaal .NET gedrag (JIT compilatie). Hiermee krijg je vooraf gecompileerde native code.

**`PublishTrimmed=true` en `TrimMode=full`**

Deze vertellen de compiler om elke code die u niet gebruikt te verwijderen. Denk aan het als het schoonmaken van uw garage voor het verplaatsen waarom pak je dingen die je niet nodig hebt?

- `PublishTrimmed=true` maakt het afknippen mogelijk
- `TrimMode=full` "Wees agressief, verwijder alles wat je kunt"

**Waarschuwing**: Dit kan code breken die gebruik maakt van zware reflectie (zoals sommige JSON serializers of ORMs) omdat de trimmer niet altijd kan zien wat je gebruikt via reflectie.

**`InvariantGlobalization=true`**

Dit verwijdert alle cultuur-specifieke gegevens van uw app formaten, valuta symbolen, tekst sorteren regels voor verschillende talen. Bespaart 5-10MB.

Stel dit alleen in op `true` als uw app:

- Gebruikt alleen Engels
- Heeft geen cultuurspecifieke datum/tijdopmaak nodig
- Gebruikt alleen ordinale (byte-byte) tekenreeksvergelijkingen

Als je een CLI-tool of API-gateway bouwt die niets om lokalisatie geeft, is dit gratis besparingen. Als je iets bouwt dat data moet formatteren voor Franse gebruikers of Turkse tekst correct sorteren, sla deze instelling dan over.

**`PublishSingleFile=true`**

Bundelt alles in één uitvoerbaar bestand.

```
myapp.exe
myapp.dll
System.Text.Json.dll
... 50 more files
```

Je krijgt gewoon:

```
myapp.exe
```

Veel makkelijker in te zetten.

**`StripSymbols=true`**

Debug symbolen helpen debuggers tonen u variabele namen en regelnummers bij het debuggen. Ze zijn nuttig tijdens de ontwikkeling, maar voeg meerdere megabytes aan uw uiteindelijke binaire.

Deze instelling verwijdert ze. Uw app draait precies hetzelfde, gewoon kleiner.

**`OptimizationPreference=Speed`**

Dit vertelt de compiler wat te prioriteren bij het nemen van beslissingen:

- `Speed`: Maak het snel (iets grotere binaire bestanden, maar betere prestaties)
- `Size`: Maak het klein (iets langzamer, maar minimaal binair formaat)

Voor de meeste toepassingen, `Speed` is de juiste keuze. Het grootteverschil is meestal slechts 2-3MB, maar het prestatieverschil kan worden opgemerkt.

**`RuntimeIdentifiers`**

Dit verklaart welke platforms u wilt ondersteunen. Het bouwt ze niet allemaal. Het vertelt alleen tooling "dit zijn geldige doelen."

Beschikbare identificatienummers:

- `win-x64`: Windows 64-bit (Intel/AMD)
- `win-arm64`: Windows ARM64 (Surface Pro X, enz.)
- `linux-x64`: Linux 64-bit (Ubuntu, Debian, RHEL, enz.)
- `linux-arm64`: Linux ARM64 (Raspberry Pi 4/5, AWS Graviton)
- `osx-x64`: MacOS Intel (oudere Macs)
- `osx-arm64`: macOS Apple Silicium (M1/M2/M3 Macs)

Je bouwt één platform tegelijk met `dotnet publish -r linux-x64`, bijvoorbeeld.

## Bouwen voor meerdere platforms

Weet je nog hoe ik zei dat AOT platformspecifieke builds vereist? Je moet apart compileren voor Windows, Linux x64, Linux ARM64, macOS Intel en macOS Apple Silicon. Dit handmatig doen zou vervelend zijn, maar we kunnen het automatiseren.

### Wat is GitHub Acties?

Als je niet bekend bent, GitHub Acties is een gratis CI/CD (Continuous Integration/Continuous Deployment) service ingebouwd in GitHub. Hiermee kun je geautomatiseerde taken uitvoeren wanneer je code pusht of een release-tag aanmaakt.

Beschouw het als het hebben van een build server die:

1. Bekijkt uw GitHub repository
2. Als je op een tag drukt als `v1.0.0`
3. Het draait automatisch Windows, Linux en macOS virtuele machines
4. Bouwt uw app voor alle platforms parallel
5. Maakt een GitHub Release aan met alle binaire bestanden

Dit alles draait op de servers van GitHub. U hoeft geen infrastructuur te onderhouden. Voor open-source projecten en kleine persoonlijke projecten is het volledig gratis.

### De volledige bouw workflow

Hier is een GitHub Acties workflow die voor alle belangrijke platforms automatisch bouwt:

```yaml
name: Build Native AOT Binaries

on:
  push:
    tags:
      - 'v*'

jobs:
  build-binaries:
    name: Build ${{ matrix.runtime }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            runtime: linux-x64
            artifact-name: myapp-linux-x64

          - os: ubuntu-latest
            runtime: linux-arm64
            artifact-name: myapp-linux-arm64

          - os: windows-latest
            runtime: win-x64
            artifact-name: myapp-win-x64

          - os: macos-latest
            runtime: osx-x64
            artifact-name: myapp-osx-x64

          - os: macos-latest
            runtime: osx-arm64
            artifact-name: myapp-osx-arm64

    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Install ARM64 tools (Linux ARM64 only)
      if: matrix.runtime == 'linux-arm64'
      run: |
        sudo apt-get update
        sudo apt-get install -y clang zlib1g-dev gcc-aarch64-linux-gnu

    - name: Publish
      shell: bash
      run: |
        # Set objcopy for ARM64 cross-compilation
        if [ "${{ matrix.runtime }}" = "linux-arm64" ]; then
          OBJCOPY_PARAM="-p:ObjCopyName=aarch64-linux-gnu-objcopy"
        else
          OBJCOPY_PARAM=""
        fi

        dotnet publish \
          -c Release \
          -r ${{ matrix.runtime }} \
          --self-contained \
          --output ./publish/${{ matrix.runtime }} \
          -p:PublishAot=true \
          -p:PublishTrimmed=true \
          -p:StripSymbols=true \
          $OBJCOPY_PARAM

    - name: Create distribution package
      shell: bash
      run: |
        mkdir -p ./dist
        cd ./publish/${{ matrix.runtime }}

        # Copy the main executable
        cp myapp${{ matrix.file-ext }} ../../dist/

        # CRITICAL: Copy native libraries (SQLite and any other native dependencies)
        # PublishSingleFile bundles .NET code, but native DLLs remain separate
        cp *.dll ../../dist/ 2>/dev/null || true
        cp *.so ../../dist/ 2>/dev/null || true
        cp *.dylib ../../dist/ 2>/dev/null || true

        # Copy config files if needed
        cp ../../appsettings.json ../../dist/ || true

        cd ../../dist

        # Create archive with all files
        if [ "${{ runner.os }}" = "Windows" ]; then
          7z a -tzip ../${{ matrix.artifact-name }}.zip *
        else
          tar czf ../${{ matrix.artifact-name }}.tar.gz *
        fi

    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: ${{ matrix.artifact-name }}
        path: |
          ${{ matrix.artifact-name }}.zip
          ${{ matrix.artifact-name }}.tar.gz
        retention-days: 7
        if-no-files-found: ignore
```

### Hoe deze workflow werkt (Stap voor stap)

Als de YAML er intimiderend uitziet, dan is dit wat het in het gewone Engels doet:

**1. Trigger (`on: push: tags`)**

De workflow draait wanneer je een Git-tag duwt die begint met `v` (zoals `v1.0.0`, `v2.3.1`Dit is de standaard manier om release versies te markeren.

**2. Matrixstrategie**

Dit is het slimme deel. In plaats van het schrijven van vijf afzonderlijke workflows, definiëren we een **matrix** van gebouwen:

- Elk gebouw krijgt een andere `os` (runner machine) en `runtime` (doelplatform)
- GitHub Acties draait alle vijf builds **parallel**
- Elk van hen produceert een artefact (het gecompileerde binaire)

Dus als je duwt `v1.0.0`, GitHub gelijktijdig:

- Spint een Ubuntu machine om Linux x64 en ARM64 te bouwen
- Draait een Windows-machine om Windows x64 te bouwen
- Spint een macOS machine om macOS x64 en ARM64 te bouwen

**3. Stappen in elke bouw**

Elk platform bouwt dezelfde stappen:

1. **Afrekencode**: Krijgt je broncode uit de repository
2. **.NET instellen**: Installeert .NET 9 SDK
3. **ARM64-gereedschappen installeren** (Alleen Linux ARM64): Installeert cross-compilation tools
4. **Publiceren**: Runs `dotnet publish` met AOT-vlaggen voor dat specifieke platform
5. **Upload artefact**: Slat het gecompileerde binaire bestand op zodat de volgende taak het kan benaderen

**4. Het resultaat**

Nadat alle builds voltooid zijn, heb je vijf artefacten (binaires) klaar om te distribueren. Je kunt ze downloaden van de Acties uitvoeren, of een tweede taak gebruiken om een GitHub Release automatisch aan te maken (niet weergegeven in dit knipsel, maar eenvoudig toe te voegen).

### Kritiek: Inheemse bibliotheken zijn niet gebundeld

Hier is iets dat me uren in de war heeft gebracht: **`PublishSingleFile=true` alleen bundels .NET code**. Inheemse bibliotheken zoals SQLite's `e_sqlite3.dll` Blijf uit elkaar.

Daarom is de stap "Create distribution package" zo belangrijk:

```bash
# Copy native libraries - these are NOT included in the main executable
cp *.dll ../../dist/ 2>/dev/null || true    # Windows
cp *.so ../../dist/ 2>/dev/null || true     # Linux
cp *.dylib ../../dist/ 2>/dev/null || true  # macOS
```

De `2>/dev/null || true` onderdeel betekent "als er geen bestanden die overeenkomen met dit patroon, niet falen gewoon doorgaan." Dit laat hetzelfde script werken op alle platforms.

**Wat je verspreidt:**

- Vensters: `myapp.exe` + `e_sqlite3.dll` (samengebundeld in een ZIP)
- Linux: `myapp` + `libe_sqlite3.so` (samengebundeld in een tar.gz)
- MacOS: `myapp` + `libe_sqlite3.dylib` (samengebundeld in een tar.gz)

Gebruikers halen het archief uit en voeren het uitvoerbare bestand uit. De native SQLite-bibliotheek zit naast het uitvoerbare bestand en de bundel vindt het automatisch op runtime.

### Waarom handmatige triggering nuttig is

Nota van de `workflow_dispatch` trigger aan de bovenkant:

```yaml
on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to publish'
        required: true
```

Hiermee kunt u bouwen handmatig triggeren vanaf de webinterface van GitHub zonder een tag te duwen. Nuttig voor het testen van de workflow of het maken van beta builds.

### ARM64 Cross-Compilatie

De Linux ARM64 build vereist speciale aandacht. Je hebt cross-compilatie tools nodig en moet de juiste `objcopy` gereedschap:

```bash
# Install tools
sudo apt-get install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu

# Build with objcopy specified
dotnet publish \
  -r linux-arm64 \
  -p:PublishAot=true \
  -p:ObjCopyName=aarch64-linux-gnu-objcopy
```

Zonder de `ObjCopyName` parameter, de koppeling faalt met cryptische fouten over onbekende bestandsformaten.

## Resultaten in de reële wereld

Hier is wat ik bereikt met mijn productie YARP-gebaseerde bot detectie gateway met middleware en SQLite logging. Dit is een echt project dat je kunt [downloaden van GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console).

### Binaire groottes (Van werkelijke GitHub releases)

Dit zijn de **werkelijke bestandsgroottes** uit mijn GitHub releases, geen theoretische schattingen:

Uitvoerbaar SQLite Native DLL (Total Archive Size)
|----------|-----------|-------------------|-------------------|
Windows x64 * 9,2MB * +1,7MB * **10,9MB** (ZIP)
Linux x64, 10,8MB +1.6MB +1.6MB **12,4MB** (tar.gz)
Linux ARM64, 9.9MB +1.5MB **11,4MB** (tar.gz)
MacOS Intel, E.E.M. +1.8MB **13,0MB** (tar.gz)
MacOS ARM64 .8MB +1.7MB +1.7MB **11,5MB** (tar.gz)

Vergelijk dit met zelfstandige .NET 9 implementaties op **130-150MB per platform**- We hebben het over een... **10-12x reductie in grootte**.

Onderverdeling:

- Hoofduitvoerbaar: Uw gecompileerde code + .NET runtime (AOT-gecompileerd)
- Native DLL: De SQLite database engine (geschreven in C)

Beide bestanden moeten samen worden verspreid, maar ze zijn nog steeds dramatisch kleiner dan traditionele .NET implementaties.

### Opstartprestaties

Koude start (eerste verzoek geserveerd) op een bescheiden Linux VPS:

- **Op zichzelf staande .NET**: ~800ms
- **Inheemse AOT**: ~150ms
- **Verbetering**: 81% sneller

Dit is van groot belang voor:

- Serverless/Lambda functies (u betaalt per milliseconde)
- Containers die vaak omhoog/omlaag schalen
- CLI-tools waar elke aanroeping nieuw begint

### Geheugengebruik

Inactief geheugen (gateway draait, geen verkeer):

- **Op zichzelf staande .NET**: 85MB
- **Inheemse AOT**: 42MB
- **Verbetering**: 51% reductie

Onder belasting (1000 verzoeken/seconde):

- **Op zichzelf staande .NET**: ~320MB
- **Inheemse AOT**: ~180MB
- **Verbetering**: 44% reductie

Ondergeheugen betekent:

- Meer containers per gastheer
- Goedkopere cloud hosting rekeningen
- Haalbaarheid op apparaten met beperkte middelen (Raspberry Pi, IoT)

## Vaak Pitfalls

### 1. Vergeten `Batteries.Init()`

Dit is de #1 fout. Zelfs met de bundel correct genoemd, vergeten te bellen `SQLitePCL.Batteries.Init()` aan het begin van uw `Main` methode zal leiden tot runtime crashes.

**De oplossing:**

```csharp
public static void Main(string[] args)
{
    SQLitePCL.Batteries.Init(); // FIRST LINE
    // ... rest of your code
}
```

### 2. Niet verdelen inheemse bibliotheken

Dit is fout nummer twee en het heeft me twee keer geraakt. `PublishSingleFile=true` bundelt GEEN native SQLite DLL's in uw uitvoerbare bestand. U moet ze verspreiden naast uw uitvoerbare bestand.

**Wat gebeurt er als je vergeet:**

- Uw app bouwt prima
- Het werkt prima op je dev machine (waar SQLite wellicht al geïnstalleerd is)
- Het crasht op productie met `DllNotFoundException`

**De oplossing:**

Bij het verpakken van uw release, altijd omvatten:

```
myapp.exe               # Your main executable
e_sqlite3.dll           # SQLite native library (Windows)
# or libe_sqlite3.so    # SQLite native library (Linux)
# or libe_sqlite3.dylib # SQLite native library (macOS)
```

In uw GitHub Acties of implementatiescripts:

```bash
# Copy ALL native libraries from the publish directory
cp *.dll ./dist/ 2>/dev/null || true
cp *.so ./dist/ 2>/dev/null || true
cp *.dylib ./dist/ 2>/dev/null || true
```

Gebruikers moeten het archief uitpakken en het uitvoerbare bestand uitvoeren. De native library moet in dezelfde map staan.

### 3. Gebruiken `winsqlite3` Aanbieder

U zou aanbevelingen kunnen zien om te gebruiken `SQLitePCLRaw.provider.winsqlite3` op Windows om de SQLite van het besturingssysteem te gebruiken. Niet doen. Het werkt alleen op Windows, vereist handmatige initialisatie, en breekt cross-platform builds. `bundle_e_sqlite3`.

### 4. Waarschuwingen trimmen met EF Core

Als u gebruik maakt van Entity Framework Core met SQLite, krijgt u trim waarschuwingen (IL2026, IL3050). Deze zijn meestal veilig te onderdrukken voor EF Core's SQLite provider, maar test grondig:

```xml
<PropertyGroup>
  <NoWarn>$(NoWarn);IL2026;IL3050</NoWarn>
</PropertyGroup>
```

Beter nog, overweeg het gebruik van Dapper of rauwe ADO.NET met SQLite voor AOT toepassingen zijn meer AOT-vriendelijk.

### 5. Ontbrekende Visual Studio Tools (Windows)

Op Windows vereist Native AOT Visual Studio's MSVC-linker. Als u buiten een Developer Command Prompt bouwt, krijgt u fouten over `vswhere.exe`. GitHub Acties behandelen dit automatisch, maar voor lokale builds, gebruik:

- Developer Command Prompt voor VS 2022
- Ontwikkelaar PowerShell voor VS 2022

Of initialiseer de omgeving in je build script:

```powershell
& "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64
dotnet publish -c Release -r win-x64
```

## Wanneer moet u Native AOT gebruiken?

Native AOT is perfect voor:

- **CLI-gereedschappen**: Instant startup zaken
- **Microdiensten en containers**: Kleinere afbeeldingen, snellere schaalvergroting
- **Randapparatuur**: Raspberry Pi, IoT apparaten met beperkte middelen
- **Serverless/Lambda**: Koude starttijd is cruciaal
- **Gateway/proxy-toepassingen**: Hoge doorvoer, lage overhead

Vermijd AOT voor:

- Heavy Entity Framework Kerngebruik (veel migraties, complexe queries)
- Reflectiezware toepassingen
- Dynamische plugin-systemen
- Toepassingen die runtime code generatie nodig hebben

## Checklist voor snelstart

Als je dit hele bericht gelezen hebt en gewoon een checklist wilt volgen, hier ga je:

**1. Voeg de pakketten toe:**

```xml
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
```

**2. Configureer uw `.csproj` voor AOT:**

```xml
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishSingleFile>true</PublishSingleFile>
<StripSymbols>true</StripSymbols>
<OptimizationPreference>Speed</OptimizationPreference>
```

**3. Initialiseer SQLite aan het begin van uw `Main` methode:**

```csharp
SQLitePCL.Batteries.Init();
```

**4. Bouwen voor uw doelplatform:**

```bash
dotnet publish -c Release -r linux-x64 --self-contained
```

**5. Test uw binaire het moet gewoon lopen zonder afhankelijkheden!**

## Conclusie

SQLite laten werken met Native AOT is niet voor de hand liggend, maar als je eenmaal de magische bezwering kent`SQLitePCLRaw.bundle_e_sqlite3` + `Batteries.Init()`Het is eenvoudig. De uitbetaling is aanzienlijk: kleine binaire bestanden, instant opstarten, en de mogelijkheid om te implementeren naar elk platform zonder runtime afhankelijkheden.

Voor iedereen die CLI-tools, gateways of edge-toepassingen met .NET bouwt, is Native AOT met SQLite nu een realistische optie. De GitHub Acties workflow die hier wordt geleverd automatiseert het gehele multi-platform bouwproces tag een release en je bent klaar.

Als je nieuw bent bij AOT, start dan klein: converteer eerst een simpele CLI tool of utility. Maak het je gemakkelijk met het bouwproces, leer wat waarschuwingen te verwachten, en begrijp de beperkingen. Zodra je de basics omlaag hebt, kun je meer complexe toepassingen aanpakken.

Het .NET ecosysteem is in toenemende mate AOT-vriendelijk. De meeste moderne bibliotheken werken ofwel uit de doos of hebben duidelijke documentatie over AOT ondersteuning. De toekomst is native, en het is sneller dan je denkt.

## Werkelijke ervaring op het gebied van de werkgelegenheid

Ik zet mijn AOT-gecompileerde gateway in om:

- **Digital Ocean Droplet (Digital Ocean Droplet)** (Linux x64) - draait als een systeemservice
- **Raspberry Pi 5** (Linux ARM64) - randimplementatie voor testen
- **Windows-server** (Windows x64) - draait als een Windows Service
- **Dockercontainers** - zowel x64 als ARM64 varianten

Het inzetproces is overal identiek:

1. Download het archief van GitHub Releases
2. Uitpakken.
3. Voer het uitvoerbare bestand uit

Geen "install .NET Runtime" stap. Geen afhankelijkheid hel. Geen versie conflicten. Gewoon uitpakken en uitvoeren.

Voor Docker, mijn `Dockerfile` is beschamend eenvoudig:

```dockerfile
FROM debian:bookworm-slim

# Copy just the two files we need
COPY minigw /app/minigw
COPY libe_sqlite3.so /app/libe_sqlite3.so

WORKDIR /app
RUN chmod +x minigw

EXPOSE 5000
ENTRYPOINT ["./minigw"]
```

De resulterende afbeelding is **~130MB** Een traditionele .NET container zou 200-250MB zijn.

## Complete werkvoorbeeld

Alles wat ik hier heb laten zien komt van een echt, productieklaar project.

- **De broncode tonen**: [Meestal lucid.BotDetection.Console](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- **Zie de GitHub acties workflow**: [console-gateway-release.yml](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)
- **Voorgebouwde binaire bestanden downloaden**: [GitHub releases](https://github.com/scottgal/mostlylucid.nugetpackages/releases)

Het project is een minimale YARP reverse proxy met bot detectie middleware die handtekeningen logt naar SQLite. Het demonstreert:

- Inheemse AOT met SQLite
- Multiplatform bouwt via GitHub-acties
- Inheemse bibliotheekbundeling
- CLI-argument ontleden
- Gestructureerde houtkap
- Graceful shutdown

Kloon het, bestudeer het, gebruik het als sjabloon voor je eigen AOT-projecten.

## Middelen

- [Officiële .NET Native AOT Documentatie](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [SQLitePCLRAw GitHub repository](https://github.com/ericsink/SQLitePCL.raw)
- [Werkvoorbeeld: Meest lucid Bot Detection Gateway](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- [Mijn GitHub acties workflow](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)

Ga nu iets kleins en snel bouwen.