Back to "Multiplatform AOT med SQLite: Hur få det att fungera"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

.NET AOT DevOps SQLite

Multiplatform AOT med SQLite: Hur få det att fungera

Tuesday, 16 December 2025

Inleiding

Native AOT lovar att göra din .NET-applikation till en liten, pytteliten, självständig, implementerad version som börjar omedelbart och kör varsomhelst. DllNotFoundException krockar , kämpar med transitiva beroenden, och undrar varför PublishSingleFile=true kan inte bundna allt i en enda file. Det här är den kompletta guiden till att få SQLite att jobba med Native AOT. , bygger på flera platformer, , och automatiserar utsläppen med GitHub actions.

Vad är AOT?

Låt oss börja med de absoluta grunderna.

Hur .NET fungerar normalt (JIT Compilation)

När du skriver C# kod och bygger din application , producerar kompilatorn inte maskinkod som din CPU kan direkt utföra Intermediat språk (IL)—se det som halvvägs mellan ditt C-kode och verkliga 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. Runtime läser din IL-kod
  3. Bara-In-Time (JITM SK3 compiler konverterar IL till inhemska maskinkod medan din app körs
  4. Din CPU utför äntligen maskinens kod

Det är som att ha en översättare som läser ditt recept (, IL, ), och översätter det verbalt till en kock. ♫ (, CPU, ), rad för rad medan de ♫

  • Runtimet för .NET är 50-150MB av extra filer som du behöver implementera
  • JIT compilern tar tid att översätta koden ( Varför är din app långsam den första gången)
  • JIT kompilatorn själv sitter i minnet medan din app körs

Enter AOT (Ahead-OfM SK2Time CompilationMSC3

Infödda AOT sätter den här modellen på huvudet.. Istället för att översätta din kod på starttid, översätter den allt vid byggtid. Du får till slut en enda lösbar file som innehåller den verkliga maskinkod som din CPU kan köra direkt

Tänk på det som att få en professionellt översatt receptbok istället för att anställa en direkt översättare.

Spelet---Changer i vinsterna

Här är vad inhemska utbildningsprogram ger dig.

1. Små utförbara: 10-30MB istället för 150MBM SK3

Din app och allt som den behöver kompileras till ett litet binärt program.

2. Omedelbart starta: 80% snabbare kylangång

Mina tester: normala .NET tog ~800ms till att starta M SK3 AOT tog ♫ ~150msM SK5 Det finns inga JIT varma apparater - uppgångstid ♫— maskinkoden är redo att köra direkt ♫

3. Null beroende: Nej .NET körtid krävs

Du kan kopiera din Executable till vilken maskinen som helst med den rätta OS: n.

4. lägre minnesmängd: Men mindre minne

Ingen JIT-kompilator som sitter i minnet. I min Gateway-applikation, normal | . | NET användes |85 | MB avlägsna | МSK4 | AOT användes

5. Bra för begränsade miljöer: Werk där JIT kan

Några miljöer (bestämda Dockercontainer , iOS , , inbyggda system

Världshandeln

AOT är "'", det inte är magiskt, ".", du "'", är flexibilitet i affärernas starttid för förstahandsoptimering.

1. Ingen dynamisk kod generering

Allt som genererar kod vid starttid kommer att fungera

  • System.Reflection.Emit ( skapa typer dynamisktM SK1
  • Dynamik assemblage laddande ( laddande DLLs vid starttid)
  • Några flotta reflektionstricks

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

2. Plattform -specifika byggningar

JIT kompilation producerar IL som kör på vilken plattform som helst. AOT producerar inhemska maskinkod för en specifik plattform. Du behöver bygga separat för

  • Windows x64
  • Linux x64
  • Linux ARM64 (Raspberry Pi)
  • macOS Intel
  • macOS Apple Silicon

Vi kan automatisera detta med GitHub Actions senare.

3. längre byggtid

Istället för att kompilera till IL i sekunder, AOT kompilerar hela vägen till maskinens kod

4. Några egenskaper behöver extra konfiguration

JSON serialisering, Enhetens ramverk , och allt som använder stor reflektion behöver du explicit säga till kompilatorn vilka typer man ska behålla

När ska du använda AOT?

Infödda AOT är perfekt för:

  • CLI-verktyg: Kommandon -inline-användbarheter där det är viktigt att starta direkt
  • Mikrotjänster: Mindre Dockerbilder , snabbare skalering i Kubernetes
  • Serverlös/Lambda: kalla starttid påverkar direkt din Rechnung
  • Kantdelar: Raspberry Pi, IoT-apparater med begränsade resurser
  • Gateway-applikationer: Omväntade proksier , API-portaal med hög hastighet
  • Desktop-verktyg: La iväg en enda .exe utan att | " | installera | . | NET första | " | steg

Skip AOT för:

  • Vanliga webapps där starttid inte spelar någon roll
  • Apps som använder ett tungt Entity Framework med många migrationer
  • Plugin-system som laddar DLLs dynamiskt
  • Allt som genererar kod vid starttid

För CLI-verktyg: ,, mikrotjänster, ,, behållare, , och randapparater,,, är förbättringarna spel, M SK4, förändringar, .. Men det finns ett problem med ' när du lägger till databaser,

SQLiteproblemet

Nu när ni förstår vad AOT är, låt oss prata om den enskilt största smärtpunkten.

Vad händer ( Den frustrerande upplevelsen)

Här är den typiska resan

  1. Du lägger till Microsoft.Data.Sqlite till ditt projekt
  2. Du konfigurerar PublishAot=true i era .csproj
  3. Byggnaden slutar utan fel— allt ser bra ut!
  4. Du kör den utförbara och får omedelbart:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Appen kraschar innan den kan göra något

Understanding Native Libraries (A Quick Detour

För att förstå problemet, behöver du veta inhemska bibliotek.

SQLite är: ' inte skriven i NET, . är skrivet i C och ' är skompilerad på plattformen.

  • e_sqlite3.dll på Windows
  • libe_sqlite3.so på Linux
  • libe_sqlite3.dylib på macOS

När du använder Microsoft.Data.Sqlite i det normala .NET, är det bara ett wrapper runt den här inhemska SQLite biblioteken . När det körs dynamiskt ladda den lämpliga moderen för din plattform.

Detta fungerar bra med normala .NET eftersom

  1. Runtime kan ladda bibliotek dynamiskt
  2. NuGet-paket kan inkludera moder bibliotek för alla plattformar
  3. Den högra blir väljad vid starttid

Varför bryter AOT detta?

Infödda AOT har två egenskaper som kolliderar med SQLite

1. Aggressiv trimning: AOT tar bort vilken kod som den tror att du använder ' du använder inte . Om det inte kan bevisa statiskt att du behöver något, så blir det löscht M SK5 Dynamiklaadning av biblioteket förvirrar trimmern MSC6 det inte klarar av kopplingen mellan din kod och den inhemska SQLite DLL

2. Ingen dynamiksladeringssupport: AOT producerar ett självbyggt binärt system - det förväntar sig att alla infödda beroenden är explicitt länkade vid kompileringstid

Resultatet: Microsoft.Data.Sqlite förväntar sig att hitta en inhemsk SQLite bibliotek vid starttid, men AOT har antingen klippt bort den eller inte vet hur man bundlar den ordentligt.

Den förvandliga beroendens mardröm

Ännu värre, om du har andra NuGet paket som använder SQLite ( som vissa ORM bibliotek eller min mostlylucid.ephemeral.complete package), du kan hamna med flera okompatibla SQLite-provider i ditt beroendeträd.

Varje leverantör försöker jobba på ett annat sätt:

  • Man kan förvänta sig att OS-ser ett SQLite ( endast WindowsM SK2
  • En annan skulle kunna byta ihop sin egen SQLite
  • En annan kanske använder en annan inhemsk bibliotekversion

AOT- kompilatorn blir förvirrad om vilken som ska användas, och ofta så är det så att den inte inkluderar någon av dem.

Lösningen: Använd paketet

Lösningen är SQLitePCLRaw.bundle_e_sqlite3— ett speciellt NuGet-paket som är speciellt utformat för att fungera med AOT

Lägg dessa två paket till ditt projekt:

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

Vad gör paketet?

Den bundle_e_sqlite3 paketet skiljer sig från vanliga SQLite-provider:

1. Det inkluderar pre-kompilerade inhemska SQLite bibliotek för varje större platform

  • Windows (x64, x 86, ARMM SK3
  • Linux (x 64, ARM64, muslM SK3baserade distros som AlpineMSC4
  • macOS (x64 Intel, ARMM SK3 Apple SiliconMSC4

2. Dessa inhemska bibliotek är paketerade på ett sätt som AOT kompilatorn förstår

Paketet markerar explicit dess ursprungliga beroenden så att AOT-kompilaren vet att inkludera dem i den slutgiltiga binären.

3. De designade för cross---platform-byggnader

Ett paket fungerar för alla plattformar. Du behöver inte ' behöver inte plattformen- specifika paket eller villkorliga referensramar

Varför det här fungerar

Kom ihåg de två problem vi identifierade

Problemet 1: AOT 's trimming tar bort bibliotek som den inte kan se som används

  • Lösningen: Paketet deklarerar explicit sina inhemska bibliotek som byggade tillgångar som måste inkluderas

Problemet 2: AOT inte supporterar dynamiska lade

  • Lösningen: Paketet länkar statiskt till SQLite biblioteken vid kompileringstid

Resultatet : när du bygger för Windows x64, inkluderar paketet e_sqlite3.dll. När du bygger för Linux så är det ARM libe_sqlite3.so. Allt fungerar

Kritiskt: Initialisera paketet M SK1Ska 'T hoppa över detta!)

Här är "'", den delen som reser upp till МSK1 av människor som försöker använda SQLite med AOT, inklusive mig på mitt första försök.

Med normala .NET, SQLite-bundeln initialiseras automatiskt den första gången du använder SQLite

Med inhemska AOT, den här automatiserade initialiseringen fungerar inte ' det funkar inte måste initialisera paketet i början av din application.

Här är den magiska gränsen du behöver

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() Do?

Den här metoden säger till SQLite-bundlet att:

  1. Hitta den korrekta ursprungliga SQLite biblioteket för din nuvarande plattform
  2. Lade in den i minnet
  3. Dra upp alla kopplingar mellan Microsoft.Data.Sqlite och den ursprungliga biblioteket

Det kallas för """, batterier, " eftersom det är paketet med batterierna. Allt man behöver är paketerat tillsammans.

Var man ska sätta det

Sätt Batteries.Init() som den allra första linjen i era Main metod , före:

  • Att skapa applikationsbyggaren
  • Att konfigurera beroende-injictioner
  • Att öppna alla databaseuppkopplingar
  • Att läsa konfigurationsfiler som kan använda SQLite

Tänk på det som att koppla in en apparat innan du försöker slå på den. Om du försöker använda SQLite innan du ska ringa Init(), du ' får fortfarande DllNotFoundException även om DLL är korrekt bundlat i din application.

Vad händer om du glömmer

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

  1. Att bygga framgångsrikt (kompilaren vann'viker att varna dig
  2. Börja köra
  3. Krockar när den försöker använda SQLite med:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Det är förvirrande eftersom DLL är bundled in i din application—it's bara inte initialiserad

Komplett projektkonfiguration

Här är "'". .csproj konfigurerad för multi-platform Native AOT med SQLite:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.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>

Nyckelregler förklarade (Vux engelskan)

Låt mig bryta ner vad varje av dessa inställningar gör

PublishAot=true

Detta är den mästerliga strömbrytaren som gör det möjligt att kompilera AOT som är infödda.. Utan detta får man normala NET-beteenden, (, JIT-kompilationer och ).. Med det här får man fram fram till

PublishTrimmed=true och TrimMode=full

De säger till kompilatorn att ta bort vilken kod som helst du inte använder. Tänk på det som att städa ut din garage innan du flyttar. Varför packa saker du inte behöver?

  • PublishTrimmed=true möjliggör trimming
  • TrimMode=full betyder " vara aggressiv , ta bort allt du kan

Varning: Det här kan bryta koden som använder tung reflektion ( som vissa JSON serialisatorer eller ORMs ) eftersom trimmern inte alltid kan se vad du userar via reflektionen

InvariantGlobalization=true

Det tar bort alla kulturer- specifika data från din app— datumformat , valutasymboler M SK3 regler för att sortera text på olika språk

Sätt bara det här till true om din app:

  • Bara använder engelska
  • Brakar inte formatering av datum och tid
  • Bara använder vanliga strings jämförelser (byte-byM SK2byte≥)

Om du bygger ett CLI-verktyg eller API-portaal som inte bryr sig om lokalisering, så är det gratis.

PublishSingleFile=true

Bundlar allt i en enda lösbar file. Istället för att ha

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

Du får bara:

myapp.exe

Mycket enklare att tillämpa.

StripSymbols=true

Debug-symboler hjälper debugger att visa variable namn och linjer när de gör ont.

Det här sättet tar bort dem. Appen kör exakt samma sak, bara mindreM SK2

OptimizationPreference=Speed

Detta säger till kompilatorn vad som ska prioriteras när man fattar beslut

  • Speed: Gör det snabbare | ( | Lite större binärar |, | men bättre resultat |
  • Size: gör den liten ( lite långsammare МSK2 men minimal binär storlek

För de flesta applikationer, Speed är det rätta valet. Storleksskillnaden är vanligtvis bara 2-3MBM SK2 men skillnaden i prestation kan vara märkbar

RuntimeIdentifiers

Det deklarerar vilka plattformar du vill stödja.

Available identifiers:

  • win-x64: Windows
  • win-arm64: Windows ARM
  • linux-x64: Linux | 64- | bit | ( | Ubuntu | , | Debian | , | RHEL |, | etc
  • linux-arm64: Linux ARM64 (Raspberry Pi ♫ ♫ 4/5, ♫ AWS Graviton ♫
  • osx-x64: macOS Intel
  • osx-arm64: macOS Apple Silicon

Du bygger en plattform i taget genom att använda dotnet publish -r linux-x64, till exempel

Byggnader för flera plattformar

Kom ihåg hur jag sa att AOT kräver plattform- specifika konstruktioner ? Du behöver kompilera separat för Windows, Linux xM SK3 Linux ARMMska4 macOS IntelMske5 och macOS Apple SiliconMskar6 Att göra detta manuell skulle vara tråkigtMskan7 men vi kan automatisera detMkan8

Vad är GitHub Actions?

Om du inte känner till: ', , GitHub Actions är en gratis CI-version.

Tänk på det som att ha en byggserver

  1. Tittar på ditt GitHub-rumslag
  2. När du trycker på ett tag som v1.0.0
  3. Den snurrar upp automatiskt Windows, ,, Linux och macOS virtuella maskiner.
  4. Bygger din app för alla Plattformar parallellt
  5. Creates a GitHub Release with all the binaries attached

Allt detta körs på GitHub.

Den kompletta byggprocessen

Här är "'", en GitHub Actions (Workflow) som bygger på alla stora platformer automatiskt.

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 den här arbetsgången fungerar ( steg för steg

Om YAML ser skrämmande ut så är det vad den gör på engelska

1. Trigger on: push: tags)

arbetsflödet körs när du trycker på en Git-tag som börjar med v ( som v1.0.0, v2.3.1). Det här är det vanliga sättet att markera releaseversioner

2. Matrixstrategi

Det här är den smarta delen, i stället för att skriva fem separata arbetsflöden, definierar vi matrix av byggnader:

  • Varje byggnad får en annan os ( löpare maskinM SK1 och runtime (target platform
  • GitHub Actions kör alla fem byggningar parallellt
  • Var och en producerar en artefakt (kompilerad binär )

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

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

3. steg i varje byggnad

Varje plattform gör samma steg

  1. Checkout-kod: Tar din källkod från arkiven
  2. Installation .NET: Installerar .NET 10 SDK
  3. Installera ARM64 verktyg (Linux ARM
  4. publicera: kör dotnet publish med AOT-flagger för den specifika plattformen
  5. Uploada artefakt: sparar kompilerade binära så att nästa jobb kan komma åt dem

4. Resultat

Efter att alla byggningar är färdiga, har du fem artefakter. ( binärar, ) redo att distribuera, . Du kan ladda ner dem från Running: Actions, or använda en andra uppgift för att skapa en GitHub-utsläpp automatiskt.

Kritiskt: Inhemska bibliotek är inte bundlade

Här är något som förvirrade mig i timmar. PublishSingleFile=true bara paket .NET kod. Infödda bibliotek som SQLite e_sqlite3.dll stanna separat.

Det är därför steget "Create distribution package " är så viktigt

# 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

Den 2>/dev/null || true del betyder " om det inte finns några filer som matchar den här mönstret , inte, ' inte misslyckas

Vad ni delar ut:

  • Windows: myapp.exe + e_sqlite3.dll ( sammanbundna i en ZIP
  • Linux: myapp + libe_sqlite3.so (bundled together in in a tar
  • macOS: myapp + libe_sqlite3.dylib (bundled together in in a tar

Användare extraherar arkiven och kör den gjorda versionen. Den inhemska SQLite biblioteket sitter bredvid den drivbara versionen, och paketet hittar den automatiskt vid starttid

Varför är manuellt triggaring användbart

Notera att workflow_dispatch löser på toppen:

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

Det låter dig aktivera byggande manuell från GitHubs webbgränssnitt, utan att trycka på tag.

ARM64 Cross-Compilation

Linux ARM64 bygger kräver särskild uppmärksamhet. Du behöver crossM SK2 kompileringsverktyg och måste specifikera den korrekta objcopy verktyg:

# 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änkaren misslyckas med kryptiska fel om okända format

Resultat i världen

Här är vad jag åstadkommit med min produktion YARP-baserad Bot-detection gateway med middleware och SQLite-loggning ladda ner från GitHub.

Binära storlekar (Vom verkliga GitHub-ut releases)

Det här är faktiska filestorlekar från mina GitHub-ut releases, inte teoretiska uppskattningar :

Plattform Rakettbart SQLite Native DLL Totala arkivstorlek
Windows x 10.9MB (ZIP
Linux x 12.4MB (tar
Linux ARM 11.4MB (tar
macOS Intel 13.0MB (tar
macOS ARM 11.5MB (tar

Vergleicha detta med självbevarade --deployment på 130-150MB per plattform. Vi talar om 10-12x minskning av storlek.

Breakdown: :

  • Det viktigaste som kan göras: : Koden du kompilerade + | . | NET runtime
  • Infödda DLL: SQLite-databassdrivaren ( skriven i C

Båda plikter måste fördelas tillsammans, men de är ännu dramatiskt mindre än traditionella NET-implementationer.

Startprestential

Cold start (first request served) on a modest Linux VPS

  • Själva | - | med |. | NET: ~800ms
  • Infödda AOT: ~150ms
  • förbättring: 81% snabbare

Detta spelar enorm roll för:".

  • Serverlös/Lambda-funktioner | ( | Du betalar per millisekunder
  • Container som skalar upp och ned frequently
  • CLI-verktyg där varje invocation börjar ny

minnesanvändning

Mindest minne ( startväg igång , ingen trafik

  • Själva | - | med |. | NET: 85MB
  • Infödda AOT: 42MB
  • förbättring: 51% minskning

Under belastning (1000 begäranM SK1sekunder ):

  • Själva | - | med |. | NET: ~320MB
  • Infödda AOT: ~180MB
  • förbättring: 44% minskning

lägre minne betyder:

  • Fler behållare per värd
  • billigare molntjänster
  • Möjligheter på resurser-begränsade enheter (Raspberry PiM SK2 IoT )

Vanliga grottor

1. Att glömma Batteries.Init()

Det här är #1 felet SQLitePCL.Batteries.Init() i början av ditt Main metoden kommer att orsaka avloppsbrott.

Fixen:

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

2. Distribuerar inte inhemska bibliotek

Det här är ett misstag #2, och det fick mig två gånger PublishSingleFile=true bundlar inte infödda SQLite DLLs i din gjorda version.. Du måste fördela dem tillsammans med din drivbara version.

Vad händer om du glömmer

  • Appen bygger bra
  • Det funkar bra på din dev-maskin (där SQLite kanske redan är installerad)
  • Det kraschar på produktionen med DllNotFoundException

Fixen:

När du packar ditt utsläpp, inkluderar du alltid:

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-aktioner eller deployment skript:

# 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 borde extrahera arkiven och köra den gjorda versionen.

3. Användande winsqlite3 Tillhandahållare

Du kanske ser tillämpningar att använda SQLitePCLRaw.provider.winsqlite3 på Windows för att använda OS: n bundle_e_sqlite3.

4. Trimvaror med EF Core

Om du använder Entity Framework Core tillsammans med SQLite, får du ",", du får trim warnings , trim warning , och de är vanligtvis säkra att trycka ned på för EF Core, ' och SQLite-leverantören.

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

Ännu bättre, överväg att använda Dapper eller rå ADO.NET med SQLite för AOT-applikationer

5. Missande visuella Studio-verktyg

På Windows requires Native AOT Visual Studio, MSVC-linker, . om du bygger utanför en Developer Command Prompt vswhere.exe. GitHub Actions hanterar det här automatiskt , men för lokala byggnader

  • Utvecklingskommandopropt för VS 2022
  • Developer PowerShell för VS 2022

Eller att initialisera miljön i ditt bygg skript:

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

När använda det ursprungliga AOT

Infödda AOT är perfekt för:

  • CLI-verktyg: Omedelbart starta
  • Mikrotjänster och behållare: Mindre bilder , snabbare skalering
  • Kantdelar: Raspberry Pi, IoT-apparater med begränsade resurser
  • Serverlös/Lambda: Cold start time is critical
  • Gateway/proxy-applikationer: Hög genomspetsning , låg överhead

undvika AOT för:

  • Heavy Entity Framework Core-användbarhet ( mängder av migrationer , komplexa queryr
  • Reflektioner-svåra tillämpningar
  • Dynamiska pluginsystem
  • Programme som behöver drivtidskod generering

Vinnig Start-kontrolllista

Om du' har läst hela den här artikeln och bara vill att en checklist ska följa

1. Lägg till paketen

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

2. Konfigurera din .csproj för AOT:

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

3. Initialisera SQLite i början av ditt Main metod:

SQLitePCL.Batteries.Init();

4. Att bygga för ditt målplattform

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

5. Testa ditt binära system

Slutsatsen

Att få SQLite att jobba med inhemska AOT är inte självklart, men när du väl känner till magincantationenSQLitePCLRaw.bundle_e_sqlite3 + Batteries.Init()—, it, ', straightforward, ., lönsamheten är substantiv, :, små binärsystem, ,, snabbstart, m sk5 och möjligheten att användas på vilken plattform som helst utan avstånd från starttid.

För alla som bygger CLI-verktyg, grindvägar , eller randanwendunger med |. |NET | , | Infödda AOT med SQLite är nu en realistisk alternativ |

Om du är ny på AOT, så startar du en liten modell, :, konverterar först ett enkelt CLI-verktyg eller -användbart. ., må bra med byggprocessen, ,, lär dig vilka varningar man ska förvänta sig, M SK5, och förstår begränsningarna,.. När du väl har gått igenom grunderna, MSC8, kan du hantera mer komplexa tillämpningar.

Ecosystemet är allt mer AOT-vänligt.

Verklig användbar upplevelse

Jag sätter igång min AOT,

  • Digital Ocean Droplet (Linux x64) - funkar som en systemad tjänst
  • Raspberry Pi 5 (Linux ARM
  • Windows-Server (Windows x
  • Dockercontainer - både x64 och ARMM SK2 varianter

Einsatzprocessen är identisk överallt:

  1. ladda ner arkiven från GitHub Releases
  2. Utvinna det
  3. kör den utförbara

Ingen versionskonflikter.

För Docker, min Dockerfile är pinsamt enkelt

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 ~130MB ( mestadels basen av Debian-bilden ). Ett traditionellt | | . | Net-container skulle vara |

Ett fungerande exempel

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

Projektet är en minimal YARP omvänd proxy med Bot-detection middleware som registrerar signer till SQLite. Det demonstrerar

  • Infödda AOT med SQLite
  • Multi-platform bygger via GitHub Actions
  • Inhemska biblioteksbundling
  • CLI argument解析
  • Strukturerad skogning
  • Ganska avstängning

Klona det , studera det, använda det som en template för dina egna AOT-projekt

resurser

Nu bygger du något litet och snabbt.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.