Back to "Multiplatform AOT avec SQLite: Comment le faire fonctionner"

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 avec SQLite: Comment le faire fonctionner

Tuesday, 16 December 2025

Introduction

L’AOT natif promet de transformer vos applications .NET en minuscules , autos - contenant des executables qui commencent instantanément et fonctionnent partout M SK3 aucune installation d’exécution n’est requise DllNotFoundException crashes, combattant avec des dépendances transitoires, et se demandant pourquoi PublishSingleFile=true doesn't actually bundle everything into a single file . This is the complete, beginnerMSC3toM SK4expert guide on getting SQLite working with Native AOTMska5 building for multiple platformsM Ska6 and automating releases with GitHub Actions M Ska7 By the endM ska8 youMske9ll have MSKA10MB binaries that run on WindowsMská11 Linux Mska12including Raspberry PiMsaka13 and macOSMaskan14with zero dependenciesMzaka15

Qu’est-ce que l’OT?

Let's commencer par les bases absoluesM SK1 Si vous' avez déjà construit une application .NET et que vous vous demandez pourquoi vous devez installer l’application \ ".NET Runtime " sur des serveurs ou pourquoi votre application de console prend un second pour commencer la première fois qu’elle est exécutée, le logiciel AOT est la réponse à ces problèmes.

Comment .NET fonctionne normalement (Compilation JIT)

Lorsque vous écrivez un code C# et que vous construisez votre application, le compiler ne produit pas de code machine que votre CPU peut exécuter directement. Langue intermédiaire (IL)—considérez-la comme la moitié de votre cheminement entre le code C # et les instructions exactes de la machine.

Lorsque vous exécutez votre .NET app, ici 's ce qui se passeM SK3

  1. Votre ordinateur charge le .NET Runtime (un programme distinct)
  2. Le temps d'exécution lit votre code IL
  3. Just-In-Time (JITM SK3 compiler convert IL en code natif de machine à mesure que votre application fonctionne
  4. Votre CPU exécute enfin ce code de la machine

C’est comme avoir un traducteur qui lit votre recette (ILM SK1 et la traduit verbalement à un chef (CPU) ligne par ligne alors qu’ilsMSC4re cuissonsMST5 Il fonctionneMSP6 mais il y aMSS7 surcoûtMSSS8

  • Le temps d'exécution .NET est de 50-150MB de fichiers supplémentaires que vous devez déployer
  • Le compilateur JIT prend du temps pour traduire le code (comment votre app est lente la première fois)
  • Le compilateur JIT lui-même se trouve dans la mémoire pendant que votre application fonctionne

Inscrire AOT (Ahead -OfM SK2Time Compilation)

AOT autochtone tourne ce modèle sur sa tête. Au lieu de traduire votre code en temps d’exécution, il traduit tout au moment de la construction. Vous obtiendrez un seul fichier exécutable qui contient le code réel de votre ordinateur et que votre CPU peut exploiter directement.

Pensez à ce qu’il s’agisse d’obtenir un livre de recettes traduit par des professionnels plutôt que d’embaucher un traducteur en direct.

Le jeu-Change des avantages

Voici ce que vous donne l’AOT autochtone

1. Moins exécutoires: 10-30MB au lieu de 150MBM SK3

Votre app et tout ce dont elle a besoin est compilé en un petit binaire. Aucun fichier de temps d'exécution distinct.

2. Démarrage immédiat: 80% début de froid plus rapide

Mes essais: normal M SK1NET a pris ~800ms pour commencer, AOT a taken ~150msMST5 Il n’y a pas de JIT warmMSC7temps d’allumageMNK8le code de la machine est prêt à être exécuté immédiatementMRK9

3. Nulle dépendance: Non .Durée de fonctionnement du réseau requise

Vous pouvez copier votre executable à n'importe quelle machine avec le bon OS (Windows/LinuxM SK2MacMSC3 et il fonctionne tout simplement . Aucune "installer | | .NET |

4. Utilisation réduite de la mémoire: À propos de 50% moins mémoire

Aucun compilateur JIT ne se trouve dans la mémoire. Dans ma application de passerelle, normal M SK2NET utilisé 85MB non fonctionnelMSC4 AOT utilisé

5. Amélioré pour les milieux restreints: Travaux où le JIT peut

Certains environnements (certains conteneurs de Docker, iOSM SK2 systèmes intégrés ) ne permettent pas la génération du code de temps d'exécution

Le commerce-offs M SK1There's Always a Catch )

L’OT n’est pas une magie, mais vous êtes une flexibilité de temps d’exécution pour l’optimisation avant-première.

1. Aucune génération de code dynamique

Tout ce qui génère des codes à l'heure d'exécution win't work:

  • System.Reflection.Emit (création de types dynamiquesM SK1
  • Chargement de l'assemblage dynamique (chargement des DLL au moment du démarrage)
  • Quelques trucs de réflexion fancieux

La plupart des codes généraux .NET sont bons, mais certains cadres qui dépendent fortement de la réflexion ont besoin d’une configuration spécialeM SK2

2. Platform-Builds spécifiques

La compilation JIT produit un IL qui fonctionne sur n’importe quelle plate-forme. AOT produit le code natif de la machine pour une plate-forme spécifique. Vous devez construire séparément pour

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

Nous ' nous aborderons de l’automatisation avec les actions de GitHub plus tard .

3. Durée de construction plus longue

Plutôt que de compiler à l’IL en secondes, AOT compile tout le chemin jusqu’au code de la machine. Atteindre M SK2 minutes au lieu de 10 secondes

4. Certaines fonctionnalités nécessitent une configuration supplémentaire

JSON serialisation, Cadre d'entités, et tout ce qui fait appel à la réflexion lourde peut nécessiter que vous dites explicitement au compiler quels types de fichiers il doit conserver.

Quand utiliser AOT?

AOT autochtone est parfait pour:

  • Outils CLI: Commande-utilités de ligne où le démarrage immédiat est important
  • Microservices: Images de docker plus petites, escalation plus rapide dans Kubernetes
  • Serverless/Lambda: Le temps de début froid a une incidence directe sur votre facture
  • Dispositifs de bord: Raspberry Pi, Dispositifs IoT à ressources limitées
  • Applications de la passerelle: Proxies inverses, passerelles API à haut débit
  • Outils de bureau: Envoyez un seul .exe sans "installer M.NET premier" étape

Passer AOT pour:

  • Applications Web traditionnelles où le temps de démarrage n’est pas important
  • Applications utilisant un cadre d'entités lourd avec de nombreuses migrations
  • Systèmes de mise en place qui chargent les DLL dynamiquement
  • Tout ce qui génère un code au moment du démarrage

Pour les outils CLI, microservices , conteneurs, et dispositifs de bordM SK3 les améliorations sont le jeuMSC4changingMST5 Mais il y aMSL6un piège quand vous ajoutez des bases de donnéesMSV7spécifiquement SQLiteM SV8

Le problème SQLite

Maintenant que vous comprenez ce qu’est un AOT, laissez ' parler du point de souffrance le plus important.

Ce qui se passe (L'expérience frustrante)

Voici le parcours typique de '

  1. Vous ajoutez Microsoft.Data.Sqlite à votre projet
  2. Vous configurez PublishAot=true dans votre .csproj
  3. La construction se termine sans erreurs—tout semble bon!
  4. Vous démarrez l’exécutoire et obtiendrez immédiatement:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Votre application se démarque avant de pouvoir faire quoi que ce soit. Ce qui donne?

Comprendre les bibliothèques autochtones (A Quick Detour)

Pour comprendre le problème, vous devez savoir Bibliothèques autochtones.

SQLite n’est pas 't écrite dans .NET—itM SK3s écrite en CMSC4 Il est 's compilé à la plate-formeMNK6code natif spécifiqueMRK7

  • e_sqlite3.dll sur Windows
  • libe_sqlite3.so sur Linux
  • libe_sqlite3.dylib sur macOS

Lorsque vous utilisez Microsoft.Data.Sqlite dans la normalité .NET, ilM SK2 est simplement un enveloppe autour de cette bibliothèque SQLite native . À l’heure du démarrageMSC4 il essaie charge dynamiquement le fichier natif approprié pour votre plate-forme.

Cela fonctionne bien avec le .NET normal parce que

  1. Le temps d'exécution peut charger les bibliothèques dynamiquement
  2. Les paquets NuGet peuvent inclure des bibliothèques natives pour toutes les plates-formes
  3. La droite est sélectionnée au moment du démarrage.

Pourquoi l’OT interrompt-il cela?

L’OT autochtone a deux caractéristiques qui s’opposent à l’approche SQLite'

1. Traitement agressif: AOT supprime tout code qu'il croit que vous utilisez ' Vous ne l'utilisez pas. Si elle peutM SK3t prouver statiquement que vous avez besoin de quelque choseMSC4 elle est suppriméeMST5 La chargement dynamique de la bibliothèque confond le trimmer MST6elle ne peutSST7voir la connexion entre votre code et le DLL natif SQLiteMst8

2. Aucun support de chargement dynamique: L'AOT produit un binaire contrôlé par lui-même - Il s'attend à ce que toutes les dépendances indigènes soient explicitement liées au moment de la compilation

Le résultat: Microsoft.Data.Sqlite attend de trouver une bibliothèque SQLite native au moment du démarrage, mais AOT l’a soit enlevée, soit ne sait pas comment la regrouper correctement

Le noyau de la dépendance transitoire

Même pire, si vous avez d’autres paquets NuGet qui utilisent SQLite (comme certaines bibliothèques ORM ou mon mostlylucid.ephemeral.complete package), vous pouvez finir par plusieurs fournisseurs SQLite incompatibles dans votre arbre de dépendance.

Chaque fournisseur essaie de travailler différemment:

  • On peut s’attendre à ce que l’OS-provide SQLite (Windows seulementM SK2
  • Un autre pourrait regrouper ses propres SQLite
  • Un autre pourrait utiliser une version de bibliothèque indigène différente.

Le compilateur AOT se méconnaît quant à l’utilisation de ce dernier, et le résultat est souvent que celui-ci ne comprend aucun d’entre eux.

La solution: Utiliser le Bundle

La solution est SQLitePCLRaw.bundle_e_sqlite3—un paquet NuGet spécial conçu spécifiquement pour travailler avec AOT

Ajouter ces deux paquets au projet:

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

Que fait le Bundle?

L'article 5 de la Loi sur l'immigration et le statut des réfugiés bundle_e_sqlite3 le paquet diffère des fournisseurs normalisés de SQLite:

1. Il comprend des bibliothèques natives SQLite pré-compilées pour chaque grande plateforme.

  • Windows (xM SK1 x 86, ARM64)
  • Linux (x 64, ARM64, muslM SK3distributions basées sur AlpineMSC4
  • macOS (x 64 Intel, ARMM SK3 Apple SiliconMSC4

2. Ces bibliothèques natives sont emballées de la manière que le compilateur AOT comprend

Le paquet marque explicitement ses dépendances natives de sorte que le compiler AOT sait les inclure dans le binaire final. Aucun chargement dynamique, aucune recherche en temps d'exécution pour les fichiers .

3. Il ' est conçu pour les bâtiments de plate-forme croisés

Un paquet fonctionne pour toutes les plates-formes. Vous n’avez pas besoin de plate-forme'ne vous avez pas besoin d’un paquet spécifique ou de références conditionnelles .

Pourquoi cela fonctionne?

N'oubliez pas les deux problèmes que nous avons identifiés?

Problème 1: AOT 's trimming remove libraries it can't see being used

  • Solution: Le paquet déclare explicitement ses bibliothèques natives comme des actifs de construction qui doivent être inclus

Problème 2: AOT ne supporte pas le chargement dynamique

  • Solution: Le paquet relie statiquement les bibliothèques SQLite au moment de la compilation

Le résultat: lorsque vous construisez pour Windows x64, le paquet inclut e_sqlite3.dll. Lorsque vous construisez pour Linux ARM64, il inclut l’ARMM SK2 libe_sqlite3.so. Tout fonctionne tout simplement .

Critical: Initialiser le Bundle (DonM SK2t Passer à ce qui suit!)

Voici la partie qui attire les gens qui tentent d’utiliser SQLite avec AOT, dont moi lors de ma première tentative.

Avec .NET normal, le paquet SQLite s’initialise automatiquement la première fois que vous utilisez SQLite . La magie se produit derrière les scènes — vous n’avez pas besoin de faire quelque chose

Avec les AOT autochtones, cette initialisation automatique ne fonctionne pas 't workM SK2 Le compiler AOT peut't voir le code de démarrage automatique (il apparaît comme un code inutilisé et est trimé doit initialiser manuellement le paquet au début de votre application.

Voici la ligne magique dont vous avez besoin.

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

Qu'est-ce qui fait Batteries.Init() Do?

Cette méthode indique le paquet SQLite à:

  1. Trouver la correcte bibliothèque native SQLite pour votre plateforme actuelle
  2. Chargez-le à la mémoire
  3. Connectez toutes les connexions entre Microsoft.Data.Sqlite et la bibliothèque autochtone

Elle est appelée "Batteries" parce que c’est 'les batteries sont inclusesM SK5l’ensemble de —tout ce dont vous avez besoin est emballé ensemble .

Où le mettre

Placer Batteries.Init() En ce qui a trait à la protection des droits de l'homme et aux libertés fondamentales, la première ligne dans votre Main méthode, avantM SK1

  • Création du constructeur d'application
  • Configuration de l'injection de dépendance
  • Ouverture de toute connexion à la base de données
  • Lecture de fichiers de configuration qui pourraient utiliser SQLite

Pensez à cela comme s'il y avait eu craquage d'un périphérique avant de le mettre en marche. Si vous essayez d'utiliser SQLite avant d'appeler Init(), vous ' recevrez toujours le DllNotFoundException même si le DLL est correctement bundlé dans votre application.

Qu’arrive-t-il si vous oubliéez?

Si vous avez oublié de téléphoner Batteries.Init(), votre app sera

  1. Construire avec succès (le compiler a remporté 'ne vous avertit pas
  2. Commencer à fonctionner
  3. Perturber le moment où elle essaie d’utiliser SQLite avec:
DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies

Cela est confusant parce que le DLL est bundled in your application—itM SK1s just not initialized.

Configuration complète du projet

Voici une liste complète de MSK .csproj Configuré pour multi platform Native AOT avec 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>

Éclaircissement des paramètres clés (En anglais simples)

Permettez-moi de vous expliquer ce que chacune de ces configurations fait:

PublishAot=true

Il s'agit de l'interrupteur principal qui permet la compilation AOT native. Sans ce , vous obtiendrez le comportement normal .NET

PublishTrimmed=true et TrimMode=full

Ils disent au compilateur de supprimer tout code que vous n’utilisez pas.

  • PublishTrimmed=true permet le triage
  • TrimMode=full signifie "être agressifM SK1lever tout ce que vous pouvez"

Avertissement: Cela peut détruire le code qui fait appel à des réflexions lourdes (comme certains serialiseurs JSON ou ORMs M SK2 car le trimmer ne peut ' pas toujours voir ce que vous utilisez via les réflexions

InvariantGlobalization=true

Cela supprime toutes les données culturelles-données spécifiques de votre appM SK1formats de date,symboles monétairesMSC3règles de tri des textes pour différentes langues . Sauvegarde MNK5MBMRK6

Seules les valeurs suivantes sont indiquées : true si votre app:

  • Utilise seulement l'anglais
  • N'est-il pas nécessaire de formatter la date spécifique de -, de la culture ou du temps?
  • Utilise seulement les comparaisons de chaînes ordinaires (byte-byM SK2byte≥)

Si vous ' construisez un outil CLI ou une passerelle API qui ne s’occupe pas de la localisation' , c’est une économie gratuiteM SK3 si vous ' construisons quelque chose qui doit formater les dates pour les utilisateurs français ou trier correctement le texte turcMSC5 reportez ce paramètreMST6

PublishSingleFile=true

Bundles tout en un seul fichier exécutoire. Au lieu d’avoir:

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

Vous obtiendrez juste:

myapp.exe

Plus facile à déployer.

StripSymbols=true

Les symboles de débugging aident les débugeurs à vous montrer des noms variables et des numéros de ligne lors du débuge. IlsM SK1 sont utiles pendant le développement mais ajoutent plusieurs mégabytes à votre binaire final.

Ce réglage les supprime. Votre app fonctionne exactement la même façonM SK1 simplement plus petite.

OptimizationPreference=Speed

Cela indique au compiler quelle priorité il doit accorder lorsqu’il prend des décisions:

  • Speed: Faire en sorte qu'il soit rapide (binoires légèrement plus grands, mais meilleure performanceM SK3
  • Size: Faire en sorte qu'il soit petit

Pour la plupart des applications, Speed est le bon choix. La différence de taille n’est habituellement que 2-3MBM SK2 mais la différence de performance peut être noticeable.

RuntimeIdentifiers

Cela indique quelles plates-formes vous voulez soutenir. Il ne les construit pas tous'il n'y a pas d'outils générauxM SK2 il dit simplement à l'outillage "ce sont des cibles validesMSC4

Identificateurs disponibles:

  • win-x64: Windows 64-bit (IntelM SK3AMD)
  • win-arm64: Windows ARMM SK1 (Surface Pro X, etcMSC4
  • linux-x64: Linux 64-bit (Ubuntu, DebianM SK4 RHELMST5 etcMSC6
  • linux-arm64: Linux ARM64 (Raspberry Pi 4/5, AWS GravitonM SK4
  • osx-x64: macOS Intel
  • osx-arm64: macOS Apple Silicon

Vous construisez une plateforme à la fois en utilisant dotnet publish -r linux-x64, par exempleM SK1

Établissement pour plusieurs plates-formes

Rappelez-vous que j’ai dit que l’OT requiert une plate-forme-constructions spécifiques ? Vous devez compiler séparément pour Windows, Linux xM SK3 Linux ARMMSC4 macOS IntelMST5 et macOS Apple SiliconMSV6 Faire cela manuellement serait acharnéMSP7mais nous pouvons le automatiserMSS8

Qu'est-ce que GitHub Actions?

Si vous 'non connaissez pas , GitHub Actions est un service gratuit de CI/CD (Intégration continue~/Déploiement continu~) intégré à Git HubM SK6 Il vous permet d'effectuer des tâches automatisées chaque fois que vous poussez le code ou que vous créez une balise de diffusionMSC7

Pensez à cela comme s’il y avait un serveur de construction qui:

  1. Surveille votre répertoire GitHub
  2. Lorsque vous poussez un étiquette comme v1.0.0
  3. Il tourne automatiquement les machines virtuelles Windows, ,, Linux et macOS.
  4. Établissez votre app pour toutes les plateformes en parallèle
  5. Crée une version de GitHub avec tous les binaires attachés

Tous ces éléments fonctionnent sur GitHub.

Le processus de travail complet pour la construction

Voici un flux de travail GitHub Actions qui construit automatiquement pour toutes les grandes plateformes.

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

Comment fonctionne ce processus de travail (Étape par étape)

Si le YAML semble intimidant, c’est ce qu’il fait en anglais simple.

1. Trigger on: push: tags)

Le flux de travail se déroule lorsque vous poussez un tag Git qui commence par v (comme v1.0.0, v2.3.1). C'est la façon standard de marquer les versions de mise en marché.

2. Stratégie de matrice

Il s’agit de la partie intelligente. Au lieu d’écrire cinq flux de travail distincts, nous définissons un matrices de constructions:

  • Chaque édifice reçoit une os (machine à courirM SK1 et runtime (plate-forme cible)
  • GitHub Actions exploite tous les cinq édifices en parallèle
  • Chacune produit un artefact (le binaire compilé)

Ainsi, lorsque vous poussez v1.0.0, GitHub simultanément:

  • Utilisez une machine Ubuntu pour construire Linux x64 et ARM64
  • Fait fonctionner une machine Windows pour construire Windows x64
  • Utilisation d'une machine macOS pour construire des systèmes x64 et ARM64

3. Étapes dans chaque bâtiment

Chaque construction de plate-forme fait les mêmes étapes:

  1. Code d'achat: Obtient votre code source du répertoire
  2. Établissement .NET: Installe un SDK
  3. Installer les outils ARM64 (Linux ARMM SK1 seulement): Installe des outils de compilation croisésMSC3
  4. Publier: Runs dotnet publish avec des drapeaux AOT pour cette plateforme spécifique
  5. Ajouter un artefact: Sauve le binaire compilé de sorte que l'emploi suivant puisse y avoir accès

4. Résultat

Après toutes les constructions terminées, vous avez cinq artefacts M SK1binaries) prêts à être distribués . Vous pouvez les télécharger de l'action RunMSC4 ou utiliser un deuxième travail pour créer automatiquement une version de GitHub |

Critical: Les bibliothèques autochtones ne sont pas groupées

Voici une chose qui m’a confondu pendant des heures. PublishSingleFile=true seulement les paquets .NET code. Bibliothèques natives comme SQLite's e_sqlite3.dll demeurer séparés.

C'est pourquoi l'étape "Create distribution package" est si importante

# 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

L'article 5 de la Loi sur l'immigration et le statut des réfugiés 2>/dev/null || true part signifie " s'il n'y a pas de fichiers qui correspondent à ce modèle , ne faites pas de faillite' ne faille pasM SK3 continuez simplementMSC4 Cela permet d'utiliser le même script sur toutes les plates-formes

Ce que vous distribuez:

  • Windows: myapp.exe + e_sqlite3.dll (bundlé dans un ZIPM SK1
  • Linux: myapp + libe_sqlite3.so (combinés dans un goudron
  • macOS: myapp + libe_sqlite3.dylib (combinés dans un goudron

Les utilisateurs extraisent l'archive et effectuent le programme exécutoire. La bibliothèque native SQLite se trouve à côté du programme executif,, et le paquet le trouve automatiquement au moment de la mise en œuvre.

Pourquoi le trigger manuelle est utile

Veuillez noter que workflow_dispatch déclencher en haut:

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

Cela vous permet de déclencher les mises en place manuellement à partir de l'interface Web du GitHub' sans pousser un étiquette. Utilisable pour tester le flux de travail ou créer des mises à niveau bêtaM SK2

ARM64 Cross-Compilation

L’architecture Linux ARM64 requiert une attention particulière . Vous avez besoin d’outils de compilation croisés- et vous devez spécifier les corrects objcopy Outil:

# 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

Sans le ObjCopyName paramètre, le linéaire échoue avec des erreurs cryptiques concernant les formats de fichier non reconnus.

Résultats mondiaux

Voici ce que j’ai réalisé avec ma production YARP-Gateway de détection des bots basé sur le middleware et l’enregistrement SQLite. C’est un projet réel que vous pouvez réaliser téléchargement de GitHub.

Les tailles binaires (Des mises à jour réelles de GitHub)

Il s’agit des tailles réelles des fichiers de mes mises à jour sur GitHub, non des estimations théoriques:

Platform Exécutable SQLite Native DLL SSK3 Total de la taille des archives S
Windows xM SK1 9.2MB 10.9MB (ZIPM SK1
Linux x 12.4MB (tarM SK1gz)
ARM Linux64 9.9MB 11.4MB (tarM SK1gz)
macOS Intel MSC2MB MSC3 13.0MB (tarM SK1gz)
macOS ARM64 9.8MB 11.5MB (tarM SK1gz)

Comparez cela à des déploiements - comportant .NET 10 au 130-150MB par plate-forme. Nous ' parlons d’un 10-12x réduction de la taille.

La ventilation:

  • Main executable: Votre code compilé + .NET runtime SSK3AOTMSC4compiledM SK5
  • DLL natif: Le moteur de base de données SQLite ( écrit en C)

Les deux fichiers doivent être distribués ensemble, mais ils sont encore beaucoup plus petits que les déploiements généraux.

Démarrage de la performance

Démarrage froid (première requête servie) sur un VPS Linux modeste :

  • Self-contained .NET: ~800ms
  • AOT autochtone: ~150ms
  • Amélioration: 81% plus rapide

Cela est extrêmement important pour :

  • Serverless/Funktions Lambda (vous paiez par millisecondeM SK2
  • Contenants qui augmentent ou diminuent fréquemment
  • Outils CLI où chaque invocation commence de nouveau

Utilisation de la mémoire

Memory inactive (gateway running, pas de traficM SK2

  • Self-contained .NET: 85MB
  • AOT autochtone: 42MB
  • Amélioration: 51% réduction

Sous charge (1000 demandes/secondeM SK2

  • Self-contained .NET: ~320MB
  • AOT autochtone: ~180MB
  • Amélioration: 44% réduction

Moins mémoire signifie:

  • Plus de conteneurs par hôte
  • Bills d'hébergement de nuages plus abordables
  • Efficacité sur les ressources-dispositifs restreints M SK1Raspberry Pi, IoT )

Les chutes communes

1. Forgetting Batteries.Init()

Il s’agit de l’erreur #1 SQLitePCL.Batteries.Init() au début de votre Main la méthode causera des défaillances de temps d'exécution.

La solution:

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

2. Les bibliothèques autochtones ne sont pas distribuées

C'est une erreur #2, et j'ai obtenu deux fois . PublishSingleFile=true ne regroupe pas les DLL natifs SQLite dans votre executable. Vous devez les distribuer à côté de l'executable.

Que se passe-t-il si vous oublierez:

  • Votre application fonctionne bien
  • Il fonctionne bien sur votre machine dev (où SQLite pourrait déjà être installée)
  • Il débouche sur la production avec DllNotFoundException

La solution:

Lorsque vous emballez votre rejet, toujours inclure:

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)

Dans vos actions GitHub ou scripts de déploiement:

# 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

Les utilisateurs devraient extraire l'archive et exécuter l'executable. La bibliothèque native doit être dans le même répertoire.

3. Usage winsqlite3 Fournisseurs

Vous pourriez voir des recommandations à utiliser SQLitePCLRaw.provider.winsqlite3 sur Windows pour utiliser l’OS-provided SQLite . Don'tM SK3 Il ne fonctionne que sur WindowsMSC4 requiert une initialisation manuelleMST5 et interrompt les croisements bundle_e_sqlite3.

4. Avertissements de triomphe avec le noyau EF

Si vous utilisez l’Entity Framework Core avec SQLite,, vous recevrez des avertissements de triage MSC3ILM SK4 IL3050). Ces avertisseurs sont habituellement sûres pour supprimer dans le cadre d’EF Core ' les fournisseurs SQLitesMNK7 mais ils doivent être vérifiés minutieusementMRK8

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

Par ailleurs, il est préférable d’utiliser le Dapper ou le ADO brut.

5. Outils d'atelier visuel manquants

Sur Windows, Native AOT requiert Visual Studio's MSVC linkerM SK2 Si vous construisez à l’extérieur d’un Command Prompt de développeur ,, vous effectuez des erreurs sur vswhere.exe. Les actions de GitHub le gèrent automatiquement, mais pour les édifices locaux

  • Demande de commande du développeur pour VS 2022
  • Developer PowerShell pour VS 2022

Ou initialiser l'environnement dans votre script de construction:

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

Quand utiliser l'AOT autochtone

AOT autochtone est parfait pour:

  • Outils CLI: Questions de démarrage immédiat
  • Microservices et conteneurs: Images plus petites , Échelle plus rapide
  • Dispositifs de bord: Raspberry Pi, Dispositifs IoT à ressources limitées
  • Serverless/Lambda: Le temps de départ froid est crucial
  • Applications Gateway/proxy: haut débit , faible surcoût

Avoid AOT for:

  • Cadre d’entités lourdes Usage du noyau (manque de migrations,questions complexes )
  • Réfléction-applications lourdes
  • Systèmes de plugin dynamique
  • Applications qui ont besoin d'une génération de code en temps d'exécution

Liste de vérification du début rapide

Si vous' avez lu tout ce post et que vous voulez simplement une liste de vérification à suivre , ici vous allez:

1. Ajouter les paquets

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

2. Configurez votre .csproj pour AOT:

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

3. Initialisez SQLite au début de votre Main méthode:

SQLitePCL.Batteries.Init();

4. Construire pour votre plate-forme cible:

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

5. Testez votre binaire—il devrait simplement fonctionner sans dépendanceM SK2

Conclusion

Faire fonctionner SQLite avec AOT autochtone n’est pas évident, mais une fois que vous connaissez l’incantation magique,SQLitePCLRaw.bundle_e_sqlite3 + Batteries.Init()—it's straightforwardM SK2 La rémunération est substantielle : petits binaires MSC4 démarrage instantané , et la capacité de déployer sur n’importe quelle plate-forme sans dépendance du temps d’exécution

Pour tous ceux qui construisent des outils CLI, passerelles , ou applications de bord avec .NETM SK3 L'OT natif avec SQLite est maintenant une option réalisteMSC4 Le flux de travail GitHub Actions fourni ici automatise l'ensemble du processus de construction multi-platformMST5M ST6juste d'étiqueter un relevé et vousMst7vous avez terminéM st8

Si vous ' êtes nouveau à l’AOT , débutez de petite taille МSK2 convertissez d’abord un outil ou une utilité CLI simple | . se sentez à l'aise avec le processus de construction |, apprenez quels sont les avertissements à attendre |

L’écosystème NET est de plus en plus AOT-friendly. La plupart des bibliothèques modernes fonctionnent soit hors de la boîte, soit disposent d’une documentation claire sur le soutien AOT.

Expérience de mise en œuvre réelle

Je déploie mon AOT-port d’accès compilé à:

  • Droplet d'océan numérique (Linux x64) - fonctionne comme un service systématique
  • Raspberry Pi 5 (Linux ARM64) - déploiement à bord pour les essais
  • Server Windows (Windows x64) - fonctionne comme un service Windows
  • Contenants Docker - toutes les variantes x64 et ARMM SK2

Le processus de déploiement est identique partout:

  1. Télécharger l'archive de GitHub Releases
  2. Extraire
  3. Exécuter le programme exécutable

Aucun "installer .NET Runtime" stepM SK3 Aucun infernal de dépendanceMST4 Aucun conflit de versionMSC5 Extraire et exécuter simplement

Pour Docker, mon Dockerfile est gênantment simple:

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'image qui en résulte est ~130MB (principalement l'image de base Debian). Un conteneur NET traditionnel . serait

Exemple de travail complet

Tout ce que j’ai montré ici provient d’une production réelle.

Le projet est un proxy YARP réversé minimal avec logiciel de détection bot qui signale les signatures à SQLite. Il démontre

  • AOT autochtone avec SQLite
  • Multi-platform builds via GitHub Actions
  • Groupage de bibliothèques autochtones
  • Analyse des arguments CLI
  • L'exploitation forestière structurée
  • Fermeture gracieuse

Il faut le cloner, l’étudier, en faire un modèle pour vos propres projets d’OTA.

Ressources

Maintenant, allez construire quelque chose petit et rapide.

logo

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