# Multipiattaforma AOT con SQLite: Come farlo funzionare!

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

Se stai cercando di spedire applicazioni .NET come singoli, eseguibili nativi utilizzando Native AOT, probabilmente hai eseguito face-first nella parete SQLite. Ottieni tutto configurato, la generazione completa con successo, e poi si blocca con crash criptico `DllNotFoundException` errori circa `e_sqlite3`. Lasciate che vi risparmi le ore di frustrazione che ho passato attraverso e mostrarvi esattamente come ottenere SQLite lavorando con Native AOT attraverso Windows, Linux (tra cui ARM64 Raspberry Pi), e macOS.

## Che cosa è AOT? (E perché dovrebbe interessarti?)

Iniziamo con le basi assolute. Se hai mai costruito un'applicazione .NET e ti sei chiesto perché è necessario installare il ".NET Runtime" sui server o perché la tua applicazione console prende un secondo per iniziare la prima volta che lo esegui, AOT è la risposta a questi problemi.

### Come funziona normalmente .NET (JIT Compilation)

Quando scrivi codice C# e costruisci la tua applicazione, il compilatore non produce codice macchina che la tua CPU può eseguire direttamente. Invece, produce qualcosa chiamato **Lingua intermedia (IL)**Pensalo come a metà strada tra il tuo codice C# e le istruzioni reali della macchina.

Quando esegui la tua app .NET, ecco cosa succede:

1. Il computer carica il runtime .NET (un programma separato)
2. Il runtime legge il tuo codice IL
3. **Just-in-time (JIT)** compilatore converte IL in codice macchina nativo come il vostro app funziona
4. La tua CPU finalmente esegue quel codice macchina

Questo è come avere un traduttore che legge la tua ricetta (IL) e la traduce verbalmente in una linea di chef (CPU) mentre stanno cucinando. Funziona, ma c'è in alto:

- Il Runtime .NET è 50-150MB di file aggiuntivi è necessario distribuire
- Il compilatore JIT richiede tempo per tradurre il codice (perché la tua app è lenta la prima volta)
- Il compilatore JIT stesso si trova in memoria durante l'esecuzione dell'app

### Inserisci AOT (compilazione in anticipo)

Nativo AOT gira questo modello sulla sua testa. Invece di tradurre il codice a runtime, traduce tutto **al tempo di generazione**. Finisci con un singolo file eseguibile che contiene codice macchina reale la tua CPU può eseguire direttamente nessun runtime, nessun traduttore, nessuna attesa.

Pensate ad esso come ottenere un libro di ricette professionalmente tradotto invece di assumere un traduttore dal vivo. Il lavoro è fatto una volta, in anticipo, e il risultato è pronto per l'uso immediatamente.

### I vantaggi che cambiano il gioco

Ecco cosa ti dà Native AOT:

**1. Piccoli eseguibili**: 10-30MB invece di 150MB+

La tua app e tutto ciò che serve viene compilato in un piccolo binario. Nessun file runtime separato.

**2. Avvio istantaneo**: 80% di partenza più veloce del freddo

I miei test: normale .NET ha preso ~800ms per iniziare, AOT ha preso ~150ms. Non c'è tempo di riscaldamento JIT è pronto per l'esecuzione immediata.

**3. Dipendenze zero**: No .NET runtime required

Puoi copiare il tuo eseguibile su qualsiasi macchina con il sistema operativo giusto (Windows/Linux/Mac) e funziona semplicemente. Nessun prerequisito "installa .NET 9 Runtime."

**4. Abbassare l'utilizzo della memoria**: Circa il 50% in meno di memoria

Nessun compilatore JIT seduto in memoria. Nella mia applicazione gateway, normale .NET usato 85MB inattivo, AOT usato 42MB.

**5. Meglio per ambienti limitati**: Funziona dove JIT non può

Alcuni ambienti (alcuni contenitori Docker, iOS, sistemi embedded) non consentono la generazione di codice runtime. AOT funziona ovunque.

### I compromessi (c'è sempre una presa)

AOT non è magico. Stai scambiando la flessibilità runtime per l'ottimizzazione upfront:

**1. Nessuna generazione di codice dinamico**

Tutto ciò che genera codice al runtime non funzionerà:

- `System.Reflection.Emit` (creando i tipi dinamicamente)
- Carico dinamico di assemblaggio (carico DLL a runtime)
- Alcuni trucchi di riflessione di fantasia

La maggior parte del normale codice .NET va bene, ma alcuni framework che si basano pesantemente sulla riflessione hanno bisogno di una configurazione speciale.

**2. Build specifici della piattaforma**

JIT compilation produce IL che funziona su qualsiasi piattaforma. AOT produce codice macchina nativo per **una piattaforma specifica**. È necessario costruire separatamente per:

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

Copriremo l'automatizzazione con le azioni GitHub piu' tardi.

**3. Tempi di costruzione più lunghi**

Invece di compilare a IL in secondi, AOT compila tutta la strada per codice macchina. Aspettatevi 2-5 minuti invece di 10 secondi. Si tratta di un costo una tantum per benefici permanenti.

**4. Alcune caratteristiche hanno bisogno di configurazione supplementare**

La serializzazione di JSON, l'Enterprise Framework e qualsiasi cosa che utilizzi un riflesso pesante potrebbe aver bisogno che tu dica esplicitamente al compilatore quali tipi tenere.

### Quando si dovrebbe utilizzare AOT?

Native AOT è perfetto per:

- **Strumenti CLI**: Utilità a riga di comando dove l'avvio istantaneo è importante
- **Microservizi**: Immagini più piccole di Docker, scalatura più veloce in Kubernetes
- **Senza server/Lambda**: Il tempo di partenza freddo influenza direttamente il vostro disegno di legge
- **Dispositivi di bordo**: Raspberry Pi, dispositivi IoT con risorse limitate
- **Applicazioni gateway**: Reverse proxys, API gateways with high throughput
- **Strumenti desktop**: Spedire un singolo .exe senza "installare .NET primo passo"

Salta AOT per:

- App web tradizionali dove il tempo di avvio non importa
- App che utilizzano un framework di entità pesante con molte migrazioni
- Sistemi plugin che caricano le DLL dinamicamente
- Qualsiasi cosa che genera codice durante il runtime

Per gli strumenti CLI, i microservizi, i contenitori e i dispositivi di bordo, i miglioramenti stanno cambiando il gioco. Ma c'è una presa quando si aggiungono i database SQLite specificamente.

## Il problema SQLite

Ora che avete capito che cosa è AOT, parliamo del singolo più grande punto dolore: SQLite. Questa è la parete che la maggior parte delle persone ha colpito quando si cerca di utilizzare AOT con applicazioni supportate dal database.

### Cosa succede (L'esperienza frustrante)

Ecco il tipico viaggio:

1. Aggiungi `Microsoft.Data.Sqlite` al tuo progetto
2. Configura `PublishAot=true` nel tuo `.csproj`
3. La costruzione completa senza errori tutto sembra buono!
4. Si esegue l'eseguibile e si ottiene immediatamente:

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

La tua app si blocca prima che possa fare qualcosa.

### Comprendere le biblioteche native (A Quick Detour)

Per capire il problema, è necessario sapere circa **librerie native**.

SQLite non è scritto in .NET è scritto in C. È compilato in codice nativo specifico della piattaforma:

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

Quando usa `Microsoft.Data.Sqlite` in normale .NET, è solo un involucro intorno a questa libreria SQLite nativo. Al runtime, si cerca di **carico dinamico** il file nativo appropriato per la piattaforma.

Questo funziona bene con normale .NET perché:

1. Il runtime può caricare le librerie dinamicamente
2. I pacchetti NuGet possono includere librerie native per tutte le piattaforme
3. Quello giusto viene selezionato al runtime

### Perché AOT rompe questo

Native AOT ha due caratteristiche che si scontrano con l'approccio di SQLite:

**1. Rifilatura aggressiva**: AOT rimuove qualsiasi codice che pensa che non stai usando. Se non può dimostrare staticamente che avete bisogno di qualcosa, viene eliminato. Caricamento libreria dinamica confonde il trimmer non può vedere la connessione tra il vostro codice e il nativo SQLite DLL.

**2. Nessun supporto di carico dinamico**: AOT produce un binario autonomo. Si aspetta che tutte le dipendenze native siano esplicitamente collegate al tempo di compilazione, non caricate dinamicamente al runtime.

Il risultato: `Microsoft.Data.Sqlite` si aspetta di trovare una libreria SQLite nativa al runtime, ma AOT l'ha tagliata via o non sa come impacchettarla correttamente.

### L'Incubo di Dipendenza Transitiva

Peggio ancora, se hai altri pacchetti NuGet che usano SQLite (come alcune librerie ORM o la mia `mostlylucid.ephemeral.complete` pacchetto), si può finire con **più fornitori di SQLite incompatibili** nel tuo albero della dipendenza.

Ogni provider cerca di lavorare in modo diverso:

- Ci si potrebbe aspettare OS-fornito SQLite (solo Windows)
- Un altro potrebbe impacchettare il proprio SQLite
- Un altro potrebbe usare una diversa versione della libreria nativa

Il compilatore AOT si confonde su quale usare, e spesso il risultato è che non include nessuno di loro, o peggio, include file in conflitto che non possono lavorare insieme.

## La soluzione: utilizzare il Bundle

Dopo ore di frustrazione, ho trovato la soluzione: `SQLitePCLRaw.bundle_e_sqlite3`. Questo è uno speciale pacchetto NuGet progettato appositamente per lavorare con AOT.

Aggiungi questi due pacchetti al tuo progetto:

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

### Cosa fa il Bundle?

La `bundle_e_sqlite3` pacchetto è diverso dai normali fornitori SQLite:

**1. Comprende le librerie SQLite native precompilate per ogni piattaforma principale:**

- Finestre (x64, x86, ARM64)
- Linux (x64, ARM64, distribuzioni basate su musl come Alpine)
- macOS (x64 Intel, ARM64 Apple Silicon)

**2. Queste librerie native sono confezionate in un modo che il compilatore AOT comprende**

Il bundle segna esplicitamente le sue dipendenze native in modo che il compilatore AOT sappia di includerle nel binario finale. Nessun caricamento dinamico, nessun runtime alla ricerca di file.

**3. È progettato per le costruzioni cross-platform**

Un pacchetto funziona per tutte le piattaforme. Non sono necessari pacchetti specifici per le piattaforme o riferimenti condizionali.

### Perché funziona

Ricordi i due problemi che abbiamo identificato?

**Problema 1: il ritaglio di AOT rimuove le librerie che non possono essere usate**

- Soluzione: il bundle dichiara esplicitamente le sue librerie native come build asset che devono essere inclusi

**Problema 2: AOT non supporta il caricamento dinamico**

- Soluzione: il bundle collega staticamente le librerie SQLite al momento della compilazione

Il risultato: quando si costruisce per Windows x64, il bundle include `e_sqlite3.dll`. Quando si costruisce per Linux ARM64, include l'ARM64 `libe_sqlite3.so`Tutto funziona e basta.

### Critico: inizializzare il Bundle (Non saltare questo!)

Qui è la parte che viaggia verso l'alto 90% della gente che prova ad usare SQLite con AOT, compreso me al mio primo tentativo.

**Con .NET normale**, il bundle SQLite si inizializza automaticamente la prima volta che si utilizza SQLite. La magia accade dietro le quinte non c'è bisogno di fare nulla.

**Con AOT nativo**, questa inizializzazione automatica non funziona. Il compilatore AOT non può vedere il codice di avvio automatico (sembra codice inutilizzato e viene rifilato), quindi si **deve inizializzare manualmente** il bundle all'inizio della tua applicazione.

Ecco la linea magica di cui hai bisogno:

```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();
    }
}
```

### Che cosa fa `Batteries.Init()` Fare?

Questo metodo dice al bundle SQLite di:

1. Trova la corretta libreria nativa SQLite per la tua piattaforma corrente
2. Caricalo nella memoria
3. Collegare tutte le connessioni tra `Microsoft.Data.Sqlite` e la libreria nativa

Si chiama "Batterie" perché è il "batterie incluse" bundle tutto ciò di cui avete bisogno è confezionato insieme.

### Dove metterlo

Metti `Batteries.Init()` come **la prima riga** nel tuo `Main` metodo, prima:

- Creazione del costruttore dell'applicazione
- Configurazione dell'iniezione di dipendenza
- Apertura di qualsiasi connessione al database
- Lettura dei file di configurazione che potrebbero usare SQLite

Pensate ad esso come collegare un dispositivo prima di provare ad accenderlo. Se si tenta di utilizzare SQLite prima di chiamare `Init()`, avrai ancora il `DllNotFoundException` anche se la DLL è correttamente in bundle nella vostra applicazione.

### Cosa succede se ti dimentichi?

Se dimentica di chiamare `Batteries.Init()`, la tua app:

1. Costruisci con successo (il compilatore non ti avvisa)
2. Inizia l'esecuzione
3. Crash il momento in cui si cerca di utilizzare SQLite con:

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

Questo è confuso perché la DLL **è** Ho perso due ore per questo errore, non essere come me.

## Configurazione completa del progetto

Qui c'è un pieno `.csproj` configurato per AOT nativo multipiattaforma con 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>
```

### Impostazioni chiave spiegate (in inglese semplice)

Lasciatemi scomporre ciò che ciascuna di queste impostazioni fa:

**`PublishAot=true`**

Questo è il master switch che consente la compilazione di Native AOT. Senza questo, si ottiene normale comportamento .NET (JIT compilazione). Con questo, si ottiene in anticipo di tempo compilato codice nativo.

**`PublishTrimmed=true` e `TrimMode=full`**

Questi dicono al compilatore di rimuovere tutto il codice che non state usando. Pensi ad esso come pulire il vostro garage prima di muoversi perché impacchettare le cose che non avete bisogno?

- `PublishTrimmed=true` consente il ritaglio
- `TrimMode=full` significa "sii aggressivo, rimuovi tutto quello che puoi"

**Attenzione**: Questo può rompere il codice che utilizza il riflesso pesante (come alcuni serializzatori JSON o ORM) perché il trimmer non può sempre vedere quello che si sta usando attraverso il riflesso. Copriremo come gestire questo in seguito.

**`InvariantGlobalization=true`**

Questo rimuove tutti i dati specifici della cultura dai formati di app, simboli di valuta, regole di ordinamento del testo per diverse lingue. Salva 5-10MB.

Imposta solo questo a `true` se la tua app:

- Usa solo l'inglese
- Non ha bisogno della formattazione della data/ora specifica per la cultura
- Usa solo i confronti ordinali (byte-byte) delle stringhe

Se stai costruendo uno strumento CLI o un gateway API che non si preoccupa della localizzazione, questo è un risparmio gratuito. Se stai costruendo qualcosa che deve formattare le date per gli utenti francesi o ordinare correttamente il testo turco, saltare questa impostazione.

**`PublishSingleFile=true`**

Bundles tutto in un unico file eseguibile. Invece di avere:

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

Tu ottieni solo

```
myapp.exe
```

Molto piu' facile da distribuire.

**`StripSymbols=true`**

I simboli di debug aiutano i debug a mostrare nomi variabili e numeri di riga durante il debug. Sono utili durante lo sviluppo, ma aggiungono diversi megabyte al binario finale.

Questa impostazione li rimuove. L'applicazione funziona esattamente lo stesso, solo più piccolo.

**`OptimizationPreference=Speed`**

Questo dice al compilatore cosa dare priorità quando si prendono le decisioni:

- `Speed`: Rendere veloce (binario leggermente più grande, ma migliori prestazioni)
- `Size`: Rendere piccolo (leggermente più lento, ma minima dimensione binaria)

Per la maggior parte delle applicazioni, `Speed` è la scelta giusta. La differenza di dimensioni è di solito solo 2-3MB, ma la differenza di prestazioni può essere evidente.

**`RuntimeIdentifiers`**

Questo dichiara quali piattaforme si desidera supportare. Esso non li costruisce tutti dice solo strumento "questi sono obiettivi validi."

Identificatori disponibili:

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

Costruisci una piattaforma alla volta usando `dotnet publish -r linux-x64`, per esempio.

## Costruzione di piattaforme multiple

Ricordate come ho detto AOT richiede build specifici per la piattaforma? È necessario compilare separatamente per Windows, Linux x64, Linux ARM64, macOS Intel, e macOS Apple Silicon. Fare questo manualmente sarebbe noioso ma possiamo automatizzare.

### Che cos'è GitHub Actions?

Se non hai familiarità, GitHub Actions è un servizio gratuito CI/CD (Integrazione continua/Distribuzione continua) integrato in GitHub. Consente di eseguire attività automatizzate ogni volta che si preme il codice o si crea un tag di rilascio.

Pensaci come se avessi un server build che:

1. Guarda il tuo repository GitHub
2. Quando si preme un tag come `v1.0.0`
3. Gira automaticamente su macchine virtuali Windows, Linux e macOS
4. Crea la tua app per tutte le piattaforme in parallelo
5. Crea una release di GitHub con tutti i binari allegati

Tutto questo funziona sui server di GitHub non è necessario mantenere alcuna infrastruttura. Per i progetti open-source e piccoli progetti personali, è completamente gratuito.

### Il flusso di lavoro di compilazione completo

Ecco un flusso di lavoro GitHub Actions che costruisce automaticamente per tutte le principali piattaforme:

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

### Come funziona questo flusso di lavoro (passo dopo passo)

Se lo YAML sembra intimidatorio, ecco cosa fa in inglese semplice:

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

Il flusso di lavoro viene eseguito quando si preme un tag Git che inizia con `v` (come `v1.0.0`, `v2.3.1`). Questo è il modo standard per contrassegnare le versioni di rilascio.

**2. Strategia della matrice**

Questa è la parte intelligente. Invece di scrivere cinque flussi di lavoro separati, definiamo un **matrice** di edifici:

- Ogni build ottiene un diverso `os` (runner machine) e `runtime` (piattaforma di destinazione)
- Azioni GitHub esegue tutti e cinque i build **in parallelo**
- Ognuno produce un manufatto (il binario compilato)

Quindi quando spingi `v1.0.0`, GitHub simultaneamente:

- Gira su una macchina Ubuntu per costruire Linux x64 e ARM64
- Gira su una macchina Windows per costruire Windows x64
- Gira su una macchina macOS per costruire macOS x64 e ARM64

**3. Passi in ogni costruzione**

Ogni piattaforma costruisce gli stessi passaggi:

1. **Codice di checkout**: Ottiene il codice sorgente dal repository
2. **Impostazione .NET**: Installa .NET 9 SDK
3. **Installare gli strumenti ARM64** (Solo Linux ARM64): Installa strumenti per la compilazione incrociata
4. **Pubblica**: Corre `dotnet publish` con flag AOT per quella specifica piattaforma
5. **Carica artefatto**: Salva il binario compilato in modo che il prossimo lavoro possa accedervi

**4. Il risultato**

Dopo tutti i build completi, hai cinque artefatti (binari) pronti per essere distribuiti. Puoi scaricarli dall'esecuzione di Azioni o usare un secondo lavoro per creare automaticamente una release GitHub (non mostrata in questo frammento, ma facile da aggiungere).

### Critico: le biblioteche native non sono cumulate

Ecco una cosa che mi ha confuso per ore: **`PublishSingleFile=true` Solo pacchetti .NET codice**. Librerie native come SQLite `e_sqlite3.dll` Restate separati.

Ecco perché il passo "Crea pacchetto di distribuzione" è così importante:

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

La `2>/dev/null || true` parte significa "se non ci sono file corrispondenti a questo modello, non fallire semplicemente continuare." Questo consente lo stesso script di lavorare su tutte le piattaforme.

**Cosa distribuisci:**

- Finestre: `myapp.exe` + `e_sqlite3.dll` (impacchettato insieme in un ZIP)
- Linux: `myapp` + `libe_sqlite3.so` (impacchettato insieme in un tar.gz)
- macOS: `myapp` + `libe_sqlite3.dylib` (impacchettato insieme in un tar.gz)

Gli utenti estraggono l'archivio ed eseguono l'eseguibile. La libreria SQLite nativa si trova accanto all'eseguibile, e il bundle lo trova automaticamente al runtime.

### Perché il trigger manuale è utile

Notare la `workflow_dispatch` grilletto in cima:

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

Questo consente di attivare manualmente la generazione dall'interfaccia web di GitHub senza premere un tag. Utile per testare il flusso di lavoro o creare build beta.

### ARM64 Cross-Compilation

Linux ARM64 build richiede un'attenzione particolare. È necessario utilizzare strumenti di cross-compilation e specificare il corretto `objcopy` strumento:

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

Senza `ObjCopyName` parametro, il linker non riesce con errori criptici sui formati di file non riconosciuti.

## Risultati del mondo reale

Ecco cosa ho ottenuto con il gateway di rilevamento bot basato su YARP di produzione con registrazione middleware e SQLite. Questo è un progetto reale che è possibile [download da GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console).

### Dimensioni binarie (da reali rilasci di GitHub)

Queste sono le **dimensioni effettive dei file** dalle mie versioni di GitHub, non stime teoriche:

| Piattaforma | Eseguibile | SQLite Native DLL | Dimensione archivio totale |
|----------|-----------|-------------------|-------------------|
| Finestre x64 | 9.2MB | +1,7MB | **10,9MB** (ZIP) |
| Linux x64 | 10,8MB | +1,6MB | **12,4MB** (tar.gz) |
| Linux ARM64 | 9,9MB | +1,5MB | **11,4MB** (tar.gz) |
| Intel macOS | 11.2MB | +1.8MB | **13,0MB** (tar.gz) |
| macOS ARM64 | 9,8MB | +1,7MB | **11,5 MB** (tar.gz) |

Confronta questo a implementazioni .NET 9 auto-contenute a **130-150MB per piattaforma**. Stiamo parlando di un **10-12x riduzione delle dimensioni**.

La ripartizione:

- Eseguibile principale: il codice compilato + .NET runtime (AOT-compiled)
- Native DLL: Il motore del database SQLite (scritto in C)

Entrambi i file devono essere distribuiti insieme, ma sono ancora drammaticamente più piccoli delle implementazioni .NET tradizionali.

### Prestazione di avvio

Cold start (prima richiesta servita) su un modesto VPS Linux:

- **.NET autonomo**: ~800ms
- **Native AOT**: ~150ms
- **Miglioramento**: 81% più veloce

Ciò è estremamente importante per:

- Funzioni Serverless/Lambda (pagate per millisecondo)
- Contenitori che scalano spesso su/giù
- Strumenti CLI dove ogni invocazione inizia fresca

### Uso della memoria

Memoria idle (gateway running, senza traffico):

- **.NET autonomo**: 85MB
- **Native AOT**: 42MB
- **Miglioramento**: riduzione del 51%

Sotto carico (1000 richieste/secondo):

- **.NET autonomo**~320MB
- **Native AOT**~180MB
- **Miglioramento**: riduzione del 44%

Abbassare la memoria significa:

- Più contenitori per host
- Biglietti di hosting cloud più economici
- Funzionalità sui dispositivi protetti dalle risorse (Raspberry Pi, IoT)

## Pitfalls comuni

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

Questo è l'errore #1. Anche con il bundle correttamente fatto riferimento, dimenticando di chiamare `SQLitePCL.Batteries.Init()` all'inizio del vostro `Main` metodo causerà crash di runtime.

**La correzione:**

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

### 2. Non Distribuire le Biblioteche Native

Questo è l'errore numero 2 e mi ha colpito due volte. `PublishSingleFile=true` NON raggruppa le DLL native SQLite nel tuo eseguibile. Devi distribuirle insieme al tuo eseguibile.

**Cosa succede se dimentica:**

- La tua app costruisce bene
- Funziona bene sulla macchina dev (dove SQLite potrebbe già essere installato)
- Si blocca sulla produzione con `DllNotFoundException`

**La correzione:**

Quando imballate il vostro rilascio, includono sempre:

```
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)
```

Nelle azioni di GitHub o negli script di implementazione:

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

Gli utenti dovrebbero estrarre l'archivio ed eseguire l'eseguibile. La libreria nativa deve essere nella stessa directory.

### 3. Utilizzo `winsqlite3` Fornitore

Potrebbe vedere raccomandazioni da usare `SQLitePCLRaw.provider.winsqlite3` su Windows per utilizzare il sistema operativo fornito SQLite. Non. Funziona solo su Windows, richiede l'inizializzazione manuale, e rompe le build cross-platform. `bundle_e_sqlite3`.

### 4. Avvisi di taglio con il nucleo EF

Se stai usando Entity Framework Core con SQLite, riceverai avvisi di trim (IL2026, IL3050). Questi di solito sono sicuri da sopprimere per il provider SQLite di EF Core, ma testare accuratamente:

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

Meglio ancora, considerare l'utilizzo di Dapper o raw ADO.NET con SQLite per le applicazioni AOT sono più AOT-friendly.

### 5. Strumenti di Visual Studio mancanti (Windows)

Su Windows, Native AOT richiede il linker MSVC di Visual Studio. Se si costruisce al di fuori di un Prompt dei comandi di sviluppo, si otterrà errori circa `vswhere.exe`. Le azioni GitHub gestiscono automaticamente questa operazione, ma per i build locali, usano:

- Prompt dei comandi dello sviluppatore per VS 2022
- Sviluppatore PowerShell per VS 2022

O inizializza l'ambiente nel tuo script di compilazione:

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

## Quando usare AOT nativo

Native AOT è perfetto per:

- **Strumenti CLI**: Questioni di avvio istantaneo
- **Microservizi e contenitori**: Immagini più piccole, scalatura più veloce
- **Dispositivi di bordo**: Raspberry Pi, dispositivi IoT con risorse limitate
- **Senza server/Lambda**: Il tempo di partenza freddo è critico
- **Applicazioni Gateway/proxy**: High throughput, low overhead

Evitare AOT per:

- Utilizzo del core Heavy Entity Framework (un sacco di migrazioni, interrogazioni complesse)
- Applicazioni pesanti per la riflessione
- Sistemi plugin dinamici
- Applicazioni che necessitano di generazione di codice runtime

## Quick Start Checklist

Se hai letto l'intero post e vuoi solo una lista di controllo da seguire, eccoti qui:

**1. Aggiungere i pacchetti:**

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

**2. Configura il tuo `.csproj` per AOT:**

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

**3. Inizializzare SQLite all'inizio del vostro `Main` metodo:**

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

**4. Costruisci per la tua piattaforma di destinazione:**

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

**5. Provare il tuo κανονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονον ονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονονοννννννονονονονονονονοννννννννννννννονονονον**

## Conclusione

Ottenere SQLite che lavora con Native AOT non è ovvio, ma una volta che si conosce l'incantesimo magico`SQLitePCLRaw.bundle_e_sqlite3` + `Batteries.Init()`Il payoff è sostanziale: piccoli binari, startup istantanea, e la possibilità di distribuire su qualsiasi piattaforma senza dipendenze runtime.

Per chiunque costruisca strumenti CLI, gateway o applicazioni di bordo con .NET, Native AOT con SQLite è ora un'opzione realistica. Il flusso di lavoro GitHub Actions fornito qui automatizza l'intero processo di compilazione multi-piattaforma.

Se sei nuovo a AOT, iniziare piccolo: convertire un semplice strumento CLI o utilità prima. Ottenere conforto con il processo di compilazione, imparare quali avvertimenti aspettarsi, e capire i limiti. Una volta che hai le basi giù, è possibile affrontare applicazioni più complesse.

L'ecosistema .NET è sempre più AOT-friendly. La maggior parte delle librerie moderne o lavorare fuori dalla scatola o avere una chiara documentazione sul supporto AOT. Il futuro è nativo, ed è più veloce di quanto si pensi.

## Esperienza reale di deployment

Dispiegare il mio AOT-compiled gateway a:

- **Digital Ocean Droplet** (Linux x64) - funziona come un servizio systemd
- **Lampone Pi 5** (Linux ARM64) - deployment edge for testing
- **Server Windows** (Windows x64) - funziona come un servizio di Windows
- **Contenitori per cani** - sia x64 che ARM64 varianti

Il processo di spiegamento è identico ovunque:

1. Scarica l'archivio da GitHub Releases
2. Estrarre
3. Esegui l'eseguibile

Nessun "installare .NET Runtime" passo. Nessuna dipendenza inferno. Nessun conflitto di versione. Basta estrarre ed eseguire.

Per Docker, mio `Dockerfile` è imbarazzantemente semplice:

```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"]
```

L'immagine risultante è **~130MB** (per lo più l'immagine Debian base). Un contenitore .NET tradizionale sarebbe 200-250MB.

## Esempio di lavoro completo

Tutto quello che ho mostrato qui proviene da un progetto vero e proprio, pronto alla produzione.

- **Visualizza il codice sorgente**: [Per lo più Lucid.BotDetection.Console](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- **Vedere il flusso di lavoro delle Azioni GitHub**: [console-gateway-release.yml](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)
- **Scarica i binari precostruiti**: [Rilascia GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/releases)

Il progetto è un reverse proxy minimo YARP con bot detection middleware che registra le firme su SQLite. Dimostra:

- Nativo AOT con SQLite
- Multi-piattaforma costruisce tramite le azioni GitHub
- Biblioteca nativa bundling
- Analisi argomento CLI
- Registrazione strutturata
- Spegnimento grazioso

Clonatelo, studiatelo, usatelo come modello per i vostri progetti AOT.

## Risorse

- [Documentazione ufficiale .NET Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [Repository SQLitePCLRaw GitHub](https://github.com/ericsink/SQLitePCL.raw)
- [Esempio di lavoro: Gateway di rilevamento delle botte per la maggior parte deilucidi](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- [Il flusso di lavoro delle mie azioni GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)

Ora vai a costruire qualcosa di piccolo e veloce.