# Multiplattform AOT med SQLite: Hur får man det att fungera!

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

Om du har försökt att skicka .NET-program som enda, inhemska körbara med Native AOT, har du förmodligen köra ansikte-först in i SQLite väggen. Du får allt konfigurerat, bygget slutförs framgångsrikt, och sedan-boom-runtime kraschar med kryptiskt `DllNotFoundException` fel om `e_sqlite3`. Låt mig spara dig timmar av frustration jag gick igenom och visa dig exakt hur man får SQLite arbetar med Native AOT över Windows, Linux (inklusive ARM64 Raspberry Pi), och macOS.

## Vad är AOT? (Och varför skulle du bry dig?)

Låt oss börja med de absoluta grunderna. Om du någonsin har byggt en .NET-applikation och undrar varför du behöver installera ".NET Runtime" på servrar eller varför din konsol app tar en andra för att starta första gången du kör den, AOT är svaret på dessa problem.

### Hur .NET fungerar normalt (JIT kompilering)

När du skriver C#-kod och bygger din applikation, producerar kompilatorn inte maskinkod som din CPU kan köra direkt. Istället, Det producerar något som kallas **Mellanspråk (IL)**-tänk på det som halvvägs mellan din C#-kod och faktiska maskininstruktioner.

När du kör din .NET app, här är vad som händer:

1. Din dator laddar .NET Runtime (ett separat program)
2. Körtiden läser din IL-kod
3. **Just-in-time (JIT)** kompilator konverterar IL till inhemsk maskinkod när din app körs
4. Din CPU kör äntligen den där maskinkoden

Detta är som att ha en översättare som läser ditt recept (IL) och verbalt översätter det till en kock (CPU) linje för linje medan de lagar mat. Det fungerar, men det finns overhead:

- .NET Runtime är 50-150MB extra filer du behöver för att distribuera
- JIT-kompilatorn tar sig tid att översätta koden (varför din app är långsam första gången)
- JIT-kompilatorn själv sitter i minnet medan appen körs

### Ange AOT (för-tid-sammanställning)

Native AOT vänder denna modell på huvudet. Istället för att översätta din kod på runtime, det översätter allt **vid byggtiden**. Du slutar med en enda körbar fil som innehåller faktiska maskinkod din CPU kan köra direkt – ingen körtid, ingen översättare, ingen väntan.

Tänk på det som att få en professionellt översatt receptbok i stället för att anställa en live översättare. Arbetet görs en gång i förväg, och resultatet är redo att användas omedelbart.

### De spel-Changing fördelar

Här är vad Native AOT ger dig:

**1. Små körbara program**: 10-30MB istället för 150MB+

Din app och allt den behöver kompileras i en liten binär. Inga separata runtime-filer.

**2. Snabbstart**: 80% snabbare kallstart

Mina tester: normala .NET tog ~800ms för att starta, AOT tog ~150ms. Det finns ingen JIT uppvärmningstid – maskinkoden är redo att köra omedelbart.

**3. Noll beroenden**: Nej . NET körtid krävs

Du kan kopiera din körbar till vilken maskin som helst med rätt OS (Windows/Linux/Mac) och den körs bara. Ingen "installera .NET 9 Runtime"-förutsättning.

**4. Lägre minnesanvändning**: Ca 50% mindre minne

Ingen JIT kompilator sitter i minnet. I min gateway program, normal .NET använde 85MB tomgång, AOT använde 42MB.

**5. Bättre för begränsade miljöer**: Fungerar där JIT inte kan

Vissa miljöer (vissa Docker behållare, iOS, inbyggda system) tillåter inte körning kodgenerering. AOT fungerar överallt.

### De kompromisser (Det finns alltid en fångst)

AOT är inte magi. Du handlar med runtime flexibilitet för förhandsoptimering:

**1. Ingen dynamisk kodgenerering**

Allt som genererar kod vid runtime kommer inte att fungera:

- `System.Reflection.Emit` (skapar dynamiska typer)
- Dynamisk monteringsbelastning (laddning av DLL-filer vid körning)
- Några fina reflektionstrick

De flesta normala .NET-kod är bra, men vissa ramar som är starkt beroende av reflektion behöver särskild konfiguration.

**2. Plattformsspecifika byggen**

JIT sammanställning producerar IL som körs på någon plattform. AOT producerar inhemsk maskinkod för **En särskild plattform**. Du måste bygga separat för:

- Fönster x64
- Linux x64
- Linux ARM64 (Raspberry Pi)
- macOS Intel
- macOS Apple Silicon

Vi tar hand om det här med GitHub-åtgärder senare.

**3. Längre byggtider**

I stället för att kompilera till IL på några sekunder kompilerar AOT hela vägen till maskinkod. Räkna med 2-5 minuter istället för 10 sekunder. Det är en engångskostnad för permanenta fördelar.

**4. Vissa funktioner behöver extra konfiguration**

JSON serialisering, Entity Framework, och allt som använder tung reflektion kan behöva dig att uttryckligen tala om för kompilatorn vilka typer att behålla. Vi täcker detta.

### När bör du använda AOT?

Native AOT är perfekt för:

- **CLI-verktyg**: Command-line verktyg där omedelbar start frågor
- **Mikrotjänster**: Mindre Docker bilder, snabbare skalning i Kubernetes
- **Serverlös/Lambda**: Kall starttid direkt påverkar din räkning
- **Kantanordningar**: Raspberry Pi, IoT-enheter med begränsade resurser
- **Gateway-tillämpningar**: Omvända proxies, API-gateways med hög genomströmning
- **Skrivbordsverktyg**: Skicka en enda .exe utan "installera .NET första" steg

Hoppa över AOT för:

- Traditionella webbappar där starttid inte spelar någon roll
- Appar som använder tunga Entity Framework med många migreringar
- Insticksprogram som laddar DLL-filer dynamiskt
- Allt som genererar kod vid körning

För CLI-verktyg, mikrotjänster, behållare och kantenheter ändras förbättringarna. Men det finns en fångst när du lägger till databaser – specifikt SQLite.

## SQLite - problemet

Nu när du förstår vad AOT är, låt oss tala om den enskilt största smärtpunkten: SQLite. Detta är väggen de flesta människor träffas när man försöker använda AOT med databas-backed program.

### Vad som händer (Den frustrerande upplevelsen)

Här är den typiska resan:

1. Du lägger till `Microsoft.Data.Sqlite` till ditt projekt
2. Du ställer in `PublishAot=true` i din `.csproj`
3. Bygget fullbordas utan fel – allt ser bra ut!
4. Du kör kör den körbara och omedelbart få:

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

Din app kraschar innan den kan göra något.

### Förstå Native Libraries (En snabb avstickare)

För att förstå problemet, måste du veta om **Inhemska bibliotek**.

SQLite är inte skrivet i .NET-det är skrivet i C. Det är sammanställt till plattformsspecifik infödd kod:

- `e_sqlite3.dll` om fönster
- `libe_sqlite3.so` på Linux
- `libe_sqlite3.dylib` om macOS

När du använder `Microsoft.Data.Sqlite` I vanliga .NET, det är bara ett omslag runt detta inhemska SQLite bibliotek. **dynamisk belastning** lämplig infödd fil för din plattform.

Detta fungerar bra med normala .NET eftersom:

1. Körtiden kan ladda bibliotek dynamiskt
2. NuGet-paket kan inkludera infödda bibliotek för alla plattformar
3. Den rätta blir vald vid körning

### Varför AOT bryter detta

Inhemska AOT har två egenskaper som strider mot SQLites strategi:

**1. Aggressiv trimning**: AOT tar bort någon kod som den tror att du inte använder. Om det inte kan statiskt bevisa att du behöver något, blir det bort. Dynamisk bibliotek laddning förvirrar trimmer-det kan inte se kopplingen mellan din kod och den inhemska SQLite DLL.

**2. Inget dynamiskt laststöd**: AOT producerar en fristående binär. Den förväntar sig att alla inhemska beroenden uttryckligen kopplas vid kompileringstid, inte laddad dynamiskt vid körning.

Resultatet: `Microsoft.Data.Sqlite` förväntar sig att hitta en infödd SQLite-bibliotek på runtime, men AOT har antingen klippt bort det eller inte vet hur man buntar det ordentligt.

### Mardrömmen om det tillfälliga beroendet

Ännu värre, om du har andra NuGet-paket som använder SQLite (som vissa ORM-bibliotek eller mina `mostlylucid.ephemeral.complete` paket), kan du sluta med **flera inkompatibla SQLite-leverantörer** i ditt beroendeträd.

Varje leverantör försöker arbeta på olika sätt:

- Man kan förvänta sig OS-tillhandahållen SQLite (endast Windows)
- En annan kan bunta sin egen SQLite
- En annan kan använda en annan infödd biblioteksversion

Den AOT kompilatorn blir förvirrad om vilken man ska använda, och ofta resultatet är att det inkluderar ingen av dem – eller värre, inkluderar motstridiga filer som inte kan arbeta tillsammans.

## Lösningen: Använd bunten

Efter timmar av frustration, hittade jag lösningen: `SQLitePCLRaw.bundle_e_sqlite3`. Detta är en speciell NuGet paket som utformats särskilt för att arbeta med AOT.

Lägg till dessa två paket till ditt projekt:

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

### Vad gör bundeln?

och `bundle_e_sqlite3` Paketet skiljer sig från vanliga SQLite leverantörer:

**1. Den innehåller förkompilerade infödda SQLite bibliotek för varje större plattform:**

- Fönster (x64, x86, ARM64)
- Linux (x64, ARM64, musl-baserade distros som alpina)
- macOS (x64 Intel, ARM64 Apple Silicon)

**2. Dessa infödda bibliotek är förpackade på ett sätt som AOT kompilatorn förstår**

Paketet markerar sina ursprungliga beroenden explicit så att AOT kompilatorn vet att inkludera dem i den slutliga binären. Ingen dynamisk laddning, ingen körning söker efter filer.

**3. Den är utformad för plattformsoberoende byggen**

Ett paket fungerar för alla plattformar. Du behöver inte plattformsspecifika paket eller villkorliga referenser.

### Varför detta fungerar

Minns du de två problemen vi identifierade?

**Problem 1: AOT:s trimning tar bort biblioteken det kan inte se att användas**

- Lösning: Paketet förklarar uttryckligen sina egna bibliotek som byggtillgångar som måste inkluderas

**Problem 2: AOT stöder inte dynamisk laddning**

- Lösning: Paketet länkar statiskt SQLite-biblioteken vid kompileringstid

Resultatet: när du bygger för Windows x64, innehåller bunten `e_sqlite3.dll`. När du bygger för Linux ARM64, innehåller det ARM64 `libe_sqlite3.so`Allt fungerar bara.

### Kritiska: Initiera bundeln ( Skippa inte detta!)

Här är den del som reser upp 90% av människor som försöker använda SQLite med AOT, inklusive mig på mitt första försök.

**Med normalt .NET**, SQLite bunten automatiskt initierar sig själv första gången du använder SQLite. Magi händer bakom kulisserna-du behöver inte göra något.

**Med Native AOT**, denna automatiska initiering fungerar inte. AOT kompilatorn kan inte se den automatiska startkoden (det ser ut som oanvänd kod och blir trimmad), så du **måste initieras manuellt** bunten i början av din ansökan.

Här är den magiska linjen du behöver:

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

### Vad gör `Batteries.Init()` Gör?

Denna metod säger SQLite bunt till:

1. Hitta rätt inbyggt SQLite-bibliotek för din nuvarande plattform
2. Ladda det i minnet
3. Koppla upp alla anslutningar mellan `Microsoft.Data.Sqlite` och det inhemska biblioteket

Det kallas "Batterier" eftersom det är "Batterier ingår" bunt – allt du behöver är förpackat tillsammans.

### Var man kan sätta den

Lägg `Batteries.Init()` som **den allra första raden** i din `Main` Metod före:

- Skapa applikationsbyggaren
- Anpassa beroendeinjektion
- Öppna alla databasanslutningar
- Läsa konfigurationsfiler som kan använda SQLite

Tänk på det som att koppla in en enhet innan du försöker slå på den. Om du försöker använda SQLite innan du ringer `Init()`, du kommer fortfarande att få `DllNotFoundException` även om DLL är korrekt buntad i din ansökan.

### Vad händer om du glömmer?

Om du glömmer att ringa `Batteries.Init()`, kommer din app:

1. Bygg framgångsrikt (kompilatorn kommer inte att varna dig)
2. Börja köra
3. Krascha ögonblicket det försöker använda SQLite med:

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

Detta är förvirrande eftersom DLL **är** Jag förlorade två timmar till detta misstag. Var inte som jag.

## Fullständig projektinställning

Här är en full `.csproj` Konfigurerad för multi-plattform Native AOT med 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>
```

### Nyckelinställningar förklarade (på klarspråk)

Låt mig dela upp vad var och en av dessa inställningar gör:

**`PublishAot=true`**

Detta är master switch som möjliggör Native AOT sammanställning. Utan detta får du normala .NET beteende (JIT sammanställning). Med detta får du i förväg kompilerad infödd kod.

**`PublishTrimmed=true` och `TrimMode=full`**

Dessa säger till kompilatorn att ta bort någon kod du inte använder. Tänk på det som att städa ur garaget innan du flyttar – varför packa saker du inte behöver?

- `PublishTrimmed=true` möjliggör trimning
- `TrimMode=full` betyder "vara aggressiv, ta bort allt du kan"

**Varning**: Detta kan bryta koden som använder tung reflektion (som några JSON serializers eller ORMs) eftersom trimmern inte alltid kan se vad du använder via reflektion. Vi täcker hur man hanterar detta senare.

**`InvariantGlobalization=true`**

Detta tar bort alla kulturspecifika data från din app – datumformat, valutasymboler, textsorteringsregler för olika språk. Sparar 5-10MB.

Ställ bara in det här till `true` om din app:

- Använder endast engelska
- Behöver inte kulturspecifik datum-/tidformatering
- Använder endast ordinalsträngsjämförelser (byte-by-byte)

Om du bygger ett CLI-verktyg eller API-gateway som inte bryr sig om lokalisering, är detta gratis besparingar. Om du bygger något som behöver formatera datum för franska användare eller sortera turkisk text korrekt, hoppa över denna inställning.

**`PublishSingleFile=true`**

Bundlar allt till en körbar fil. Istället för att ha:

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

Du får bara:

```
myapp.exe
```

Mycket lättare att använda.

**`StripSymbols=true`**

Felsökningssymboler hjälper felsökare att visa dig variabla namn och radnummer vid felsökning. De är användbara under utvecklingen men lägger till flera megabyte till din sista binär.

Denna inställning tar bort dem. Din app körs exakt samma, bara mindre.

**`OptimizationPreference=Speed`**

Detta talar om för kompilatorn vad man ska prioritera när man fattar beslut:

- `Speed`: Gör det snabbt (lite större binärer, men bättre prestanda)
- `Size`: Gör det litet (lite långsammare, men minimal binär storlek)

För de flesta tillämpningar, `Speed` är rätt val. Storleksskillnaden är oftast bara 2-3MB, men prestandaskillnaden kan vara märkbar.

**`RuntimeIdentifiers`**

Detta förklarar vilka plattformar du vill stödja. Det bygger inte alla – det säger bara verktyg "dessa är giltiga mål."

Tillgängliga identifierare:

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

Du bygger en plattform åt gången med hjälp av `dotnet publish -r linux-x64`, till exempel.

## Bygga för flera plattformar

Minns du hur jag sa att AOT kräver plattformsspecifika byggen? Du måste kompilera separat för Windows, Linux x64, Linux ARM64, macOS Intel och macOS Apple Silicon. Att göra detta manuellt skulle vara tråkigt – men vi kan automatisera det.

### Vad är GitHub-åtgärder?

Om du inte känner till GitHub Actions är en gratis CI/CD-tjänst (Continuous Integration/Continuous Deployment) inbyggd i GitHub. Den låter dig köra automatiserade uppgifter när du trycker på koden eller skapar en releasetag.

Tänk på det som att ha en byggserver som:

1. Tittar på ditt GitHub arkiv
2. När du trycker på en tagg som `v1.0.0`
3. Det snurrar automatiskt upp Windows, Linux och macOS virtuella maskiner
4. Bygger din app för alla plattformar parallellt
5. Skapar en GitHub Release med alla bifogade binärer

Allt detta körs på GitHubs servrar – du behöver inte underhålla någon infrastruktur. För projekt med öppen källkod och små personliga projekt är det helt gratis.

### Det kompletta byggarbetsflödet

Här är ett arbetsflöde för GitHub Actions som automatiskt bygger för alla större plattformar:

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

### Hur detta arbetsflöde fungerar (steg för steg)

Om YAML ser skrämmande ut, så här gör den på engelska:

**1. Utlösare (`on: push: tags`)**

Arbetsflödet körs när du trycker på en Git-tagge som börjar med `v` (som `v1.0.0`, `v2.3.1`. . Detta är det vanliga sättet att markera versionerna.

**2. Matrisstrategi**

Detta är den smarta delen. I stället för att skriva fem separata arbetsflöden, definierar vi en **matris** Byggnader:

- Varje byggnad får en annan `os` (föregångsmaskin) och `runtime` (målplattform)
- GitHub-åtgärder kör alla fem byggen **parallellt**
- Var och en producerar en artefakt (kompilerade binära)

Så när du trycker `v1.0.0`, GitHub samtidigt:

- Spins upp en Ubuntu-maskin för att bygga Linux x64 och ARM64
- Spins upp en Windows-maskin för att bygga Windows x64
- Spins upp en macOS-maskin för att bygga macOS x64 och ARM64

**3. Steg i varje bygge**

Varje plattform bygger gör samma steg:

1. **Utcheckningskod**: Hämtar källkoden från arkivet
2. **Ställ in .NET**: Installerar .NET 9 SDK
3. **Installera verktyg för ARM64** (endast Linux ARM64): Installerar verktyg för korskompilering
4. **Publicera**: Kör `dotnet publish` med AOT-flaggor för den specifika plattformen
5. **Ladda upp artefakt**: Sparar kompilerad binär så att nästa jobb kan komma åt det

**4. Resultatet**

När alla bygg färdig, du har fem artefakter (binärer) redo att distribuera. Du kan ladda ner dem från åtgärderna kör, eller använda ett andra jobb för att skapa en GitHub Release automatiskt (inte visas i denna text, men lätt att lägga till).

### Kritiska: Inhemska bibliotek är inte förenade

Här är något som förvirrat mig i timmar: **`PublishSingleFile=true` Endast paket .NET-kod**. Inhemska bibliotek som SQLite's `e_sqlite3.dll` Håll dig ifrån varandra.

Därför är steget "Skapa distributionspaket" så viktigt:

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

och `2>/dev/null || true` Del betyder "om det inte finns några filer som matchar detta mönster, misslyckas inte – fortsätt bara." Det låter samma skript fungera på alla plattformar.

**Vad du distribuerar:**

- Fönster: `myapp.exe` + `e_sqlite3.dll` (tillsammans i en ZIP)
- Linux: Vad är det? `myapp` + `libe_sqlite3.so` (tillsammans i en tjär.gz)
- - Vad är det med dig? `myapp` + `libe_sqlite3.dylib` (tillsammans i en tjär.gz)

Användare extraherar arkivet och kör kör det körbara. Det ursprungliga SQLite-biblioteket ligger bredvid det körbara, och bunten hittar det automatiskt vid körtid.

### Varför manuell triggning är användbart

Lägg märke till `workflow_dispatch` utlösning högst upp:

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

Detta låter dig utlösa byggen manuellt från GitHubs webbgränssnitt utan att trycka på en tagg. Användbart för att testa arbetsflödet eller skapa beta-byggen.

### ARM64 Korskompilering

Linux ARM64 bygger kräver särskild uppmärksamhet. Du behöver korskompileringsverktyg och måste ange rätt `objcopy` Verktyg:

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

Utan `ObjCopyName` parameter, länken misslyckas med kryptiska fel om oigenkännliga filformat.

## Verkliga resultat

Här är vad jag uppnådde med min produktion YARP-baserade bot detektering gateway med middleware och SQLite loggning. Detta är ett riktigt projekt du kan [nedladdning från GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console).

### Binära storlekar (från faktiska GitHub-utgåvor)

Det här är **Verkliga filstorlekar** från min GitHub releaser, inte teoretiska uppskattningar:

till Plattform  och körbar  på SQLite Native DLL  till Total Archive Size
|----------|-----------|-------------------|-------------------|
på Windows x64  och 9.2MB  på +1,7MB **10,9 Mbyte** (ZIP) (ZIP) (ZIP) (ZIP) (ZIP))
på Linux x64  och 10.8MB  på +1.6MB **12,4 Mbyte** (tar.gz)  till
.. Linux ARM64 .9.9MB .. +1.5MB .. **11,4 Mbyte** (tar.gz)  till
till macOS Intel  på 11,2MB  på +1,8MB **13,0 Mbyte** (tar.gz)  till
MACOS ARM64  och 9.8 Mbyte +1,7 Mbyte . **11,5 Mbyte** (tar.gz)  till

Jämför detta med fristående .NET 9 utbyggnader vid **130-150 MB per plattform**. Vi pratar om en **10-12x minskning av storlek**.

Fördelningen:

- Huvudkörbar: Din kompilerade kod + .NET körtid (AOT-kompilerad)
- Native DLL: SQLite databasmotorn (skriven i C)

Båda filerna måste distribueras tillsammans, men de är fortfarande dramatiskt mindre än traditionella .NET-distributioner.

### Uppstartsprestanda

Kall start (första begäran serverad) på en blygsam Linux VPS:

- **Självförsörjande .NET**: ~800ms
- **Inhemsk AOT**: ~150ms
- **Förbättring**: 81% snabbare

Detta är av stor betydelse för följande:

- Serverless/Lambda-funktioner (du betalar per millisekund)
- Behållare som ofta skalas upp/ner
- CLI-verktyg där varje anrop börjar fräscht

### Minnesanvändning

Tomgångsminne (gateway körs, ingen trafik):

- **Självförsörjande .NET**: 85 MB
- **Inhemsk AOT**: 42MB
- **Förbättring**: 51 % minskning

Under belastning (1000 förfrågningar/sekund):

- **Självförsörjande .NET**: : : : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- **Inhemsk AOT**: ~180MB
- **Förbättring**: 44 % minskning

Lägre minne innebär:

- Fler behållare per värd
- Billigare moln hosting räkningar
- Genomförbarhet på resursbegränsade enheter (Raspberry Pi, IoT)

## Vanliga fall

### 1. Glömma `Batteries.Init()`

Detta är det # 1 misstaget. Även med bunten korrekt refererade, glömma att ringa `SQLitePCL.Batteries.Init()` i början av din `Main` metoden kommer att orsaka runtime krascher.

**Rättningen:**

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

### 2. Inte distribuera Native Libraries

Det här är misstag nummer 2, och det fick mig två gånger. `PublishSingleFile=true` buntar INTE in inhemska SQLite- DLLer i din körbara. Du måste distribuera dem tillsammans med din körbara.

**Vad händer om du glömmer:**

- Din app bygger bra
- Det går bra på din dev maskin (där SQLite kanske redan är installerad)
- Det kraschar på produktionen med `DllNotFoundException`

**Rättningen:**

När du packar din release, alltid inkludera:

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

I dina GitHub-åtgärder eller distributionsskript:

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

Användare bör extrahera arkivet och köra körbar. Det ursprungliga biblioteket måste vara i samma katalog.

### 3. Användning `winsqlite3` Leverantör

Du kan se rekommendationer att använda `SQLitePCLRaw.provider.winsqlite3` på Windows för att använda OS-tillhandahållen SQLite. Inte. Det fungerar bara på Windows, kräver manuell initiering, och bryter plattformsoberoende byggen. `bundle_e_sqlite3`.

### 4. Trimvarningar med EF Core

Om du använder Entity Framework Core med SQLite får du trimvarningar (IL2026, IL3050). Dessa är vanligtvis säkra att undertrycka för EF Core s SQLite leverantör, men testa noggrant:

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

Ännu bättre, överväga att använda Dapper eller rå ADO.NET med SQLite för AOT-program-de är mer AOT-vänlig.

### 5. Saknar visuella studioverktyg (Windows)

På Windows, Native AOT kräver Visual Studio s MSVC länkare. Om du bygger utanför en utvecklarkommando Prompt, får du fel om `vswhere.exe`. GitHub Åtgärder hanterar detta automatiskt, men för lokala byggen, använd:

- Utvecklarkommando Prompt för VS 2022
- Utvecklare PowerShell för VS 2022

Eller initiera miljön i byggskriptet:

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

## När du ska använda Native AOT

Native AOT är perfekt för:

- **CLI-verktyg**: Snabbstart frågor
- **Mikrotjänster och containrar**: Mindre bilder, snabbare skalning
- **Kantanordningar**: Raspberry Pi, IoT-enheter med begränsade resurser
- **Serverlös/Lambda**: Kall starttid är avgörande
- **Gateway/proxy-tillämpningar**: Högt genomflöde, lågt övertryck

Undvik AOT för:

- Heavy Entity Framework Core-användning (många migreringar, komplexa frågor)
- Reflektionstunga tillämpningar
- Dynamiska instickssystem
- Tillämpningar som behöver runtime-kodgenerering

## Checklista för snabbstart

Om du har läst hela detta inlägg och bara vill ha en checklista att följa, här går du:

**1. Lägg till paketen:**

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

**2. Anpassa din `.csproj` för AOT:**

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

**3. Initiera SQLite i början av din `Main` Metod:**

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

**4. Bygg för din målplattform:**

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

**5. Testa din binär – det ska bara köras utan beroenden!**

## Slutsatser

Att få SQLite att arbeta med Native AOT är inte uppenbart, men när man väl känner till den magiska besvärjelsen`SQLitePCLRaw.bundle_e_sqlite3` + `Batteries.Init()`Vinsten är betydande: små binärer, omedelbar start och möjligheten att placera ut till vilken plattform som helst utan driftstidsberoenden.

För alla som bygger CLI-verktyg, gateways eller edge-applikationer med .NET är Native AOT med SQLite nu ett realistiskt alternativ. arbetsflödet GitHub Actions som tillhandahålls här automatiserar hela multiplattformsbyggprocessen – tagga bara en release och du är klar.

Om du är ny på AOT, börja små: konvertera en enkel CLI verktyg eller verktyg först. Bli bekväm med byggprocessen, lära dig vilka varningar att förvänta sig, och förstå begränsningarna. När du har fått grunderna nere, kan du hantera mer komplexa program.

.NET ekosystem är allt mer AOT-vänlig. De flesta moderna bibliotek antingen fungerar ur lådan eller har tydlig dokumentation på AOT stöd. Framtiden är infödd, och det är snabbare än du tror.

## Verklig erfarenhet av utplacering

Jag använder min AOT-kompilerade port till:

- **Digital oceandropp** (Linux x64) - körs som en systemd tjänst
- **Hallon Pi 5** (Linux ARM64) - Kantutbyggnad för testning
- **Windows- serverName** (Windows x64) - körs som en Windows-tjänst
- **Behållare för dockor** - både x64 och ARM64 varianter

Utplaceringsprocessen är densamma överallt:

1. Ladda ner arkivet från GitHub releaser
2. Extrahera den
3. Kör det körbara programmet

Inget "installera .NET Runtime" steg. Inget beroende helvete. Inga versionskonflikter. Bara extrahera och köra.

För Docker, min `Dockerfile` är pinsamt enkelt:

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

Den resulterande bilden är **Om ja, ange i tillämpliga fall vilka åtgärder som ska vidtas för att säkerställa att de åtgärder som föreskrivs i denna förordning är förenliga med den inre marknaden.** (huvudsakligen Debians basavbildning). En traditionell .NET-behållare skulle vara 200-250 MB.

## Fullständigt arbetsexempel

Allt jag har visat här kommer från ett riktigt, produktionsklart projekt.

- **Visa källkoden**: [Mestlylucid.BotDetection.Console](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- **Se arbetsflödet GitHub Actions**: [konsol-gateway-release.yml](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)
- **Ladda ner förbyggda binärer**: [Utgåvor av GitHub](https://github.com/scottgal/mostlylucid.nugetpackages/releases)

Projektet är en minimal YARP omvänd proxy med bot upptäckt middleware som loggar signaturer till SQLite. Det visar:

- Inhemsk AOT med SQLite
- Multiplattformsbyggen via GitHub-åtgärder
- Inhemsk biblioteksbundling
- CLI-argumentets tolkning
- Strukturerad loggning
- Förtjusande avstängning

Klona den, studera den, använd den som mall för dina egna AOT-projekt.

## Resurser

- [Officiella .NET Native AOT dokumentation](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [SQLitePCLaraw GitHub Repository](https://github.com/ericsink/SQLitePCL.raw)
- [Arbetsexempel: Mestlylucid Bot Detection Gateway](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- [Mina GitHub-åtgärder Workflow](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)

Gå och bygg något litet och snabbt.