# 与 SQLite 的多平台 AOT : 如何运作 !

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

如果您一直试图将 .NET 应用程序作为使用本地 AOT 的单一本地可执行程序装运, 您很可能已经将脸部排在 SQLite 墙上。 您得到所有配置, 建筑完成成功, 然后用加密的自动崩溃 。 `DllNotFoundException` 有关错误的错误 `e_sqlite3`让我给你省下我所经历的挫折的时光 并告诉你如何让SQLite 和原住民AOT合作 穿过Windows、Linux(包括ARM64 Raspberry Pi)和MacOS。

## 什么是AOT?

让我们从绝对基础开始。 如果您曾经建立过 .NET 应用程序, 并想知道为什么您需要在服务器上安装“ .NET Runtime ” , 或者为什么您的控制台应用程序第一次运行时需要一秒钟才能启动, AOT 是这些问题的答案 。

### 如何.NET正常工作(JIT汇编)

当您写入 C# 代码并构建您的应用程序时, 编译器不会生成您的 CPU 可以直接执行的机器代码。 相反, 它会生成一个名为 **中间语言(IL)**- 把它当成你C#代码 和机器实际指示的中间点

当你运行您的. NET 应用程序时, 下面是这样的:

1. 您的计算机装入.NET 运行时间( 一个单独的程序)
2. 运行时间读取您的 IL 代码
3. **只在时(JIT)** 编译器将您的应用程序运行时的 IL 转换为本地机码
4. 你的CPU终于执行机器代码了

这就像有一个翻译 读你的食谱(IL) , 并用口头翻译 在厨师(CPU)做饭时用线条 将它翻译为厨师(CPU)。

- NET 运行时间为 50 - 150MB 。 您需要部署的额外文件
- JIT 编译器需要时间翻译代码( 为什么您的应用程序第一次减慢)
- JIT 编译器本身在您的应用程序运行时处于记忆中

### Enter AOT(《时前汇编》)

AOT 原生 AOT 将这个模型翻翻到头顶上。 它不是在运行时翻译您的代码, 而是翻译一切 **建设时间**。最后,你有一个包含实际机器代码的可执行文件。您的CPU可以直接运行,没有运行时间,没有翻译,没有等待。

将它想象成获得一本专业翻译的食谱,而不是雇用一名现场翻译。 工作就完成了一次,提前完成,结果可以立即使用。

### 游戏变换福利

以下是原住民AOT给你的:

**1. 微小可执行文件**:10-30MB,而不是150MB+

您的应用程序和它所需要的一切 都汇编成一个小二进制文件。 没有单独的运行时间文件 。

**2. 即时启动**:80%更快的寒冷开始

我的测试是:正常的.NET用~800米开始,AOT用~150米。没有JIT热身时间,机器代码可以立即执行。

**3. 零依赖性**: 不需要.NET运行时间

您可以将您的可执行文件复制到任何机器, 并使用正确的 OS (Windows/ Linux/Mac) , 并且它正在运行 。 没有“ 安装. NET 9 Runtime ” 的先决条件 。

**4. 内存使用率降低**: 将内存减50%左右

内存中没有 JIT 编译器。 在我的网关应用程序中, 普通的. NET 使用 85MB 闲置, AOT 使用 42MB 。

**5. 改善限制环境**在JIT不能工作的地方工作

有些环境(某些多克集装箱、iOS、嵌入系统)不允许运行时间代码生成。 AOT到处工作。

### 权衡(总是有收获的)

AOT不是魔法,你用运行时的灵活度来交换前期优化:

**1. 没有动态代码生成**

任何在运行时生成代码的代码都不会有效 :

- `System.Reflection.Emit` (动态生成型号)
- 动态组装装入(运行时装入 DLL)
- 一些花哨的思考技巧

多数正常的.NET代码是好的,但一些非常依赖反省的框架需要特殊配置。

**2. 针对具体平台的建设**

JIT编集产生在任何平台上运行的 IL 。 AOT 生成本地机码 **一个特定平台**。您需要为以下目的单独建房:

- Windows x64
- Linux x64
- Linux ARM64 (草莓皮)
- MACOS 英特尔
- 苹果硅

我们稍后会和吉特Hub Action 一起报道这个自动处理

**3. 较长的建筑时间**

AOT没有在几秒钟内编集到 IL, 而是一直编集到机器代码。 期望 2 - 5 分钟而不是 10 秒。 这是一次性的永久收益成本 。

**4. 有些特征需要额外配置**

JSON 序列化,实体框架, 以及任何使用重反射的东西 可能需要你明确告诉编译者 哪些类型要保留。 我们将覆盖这个类型 。

### 你什么时候用AOT?

土著AOT 完美地适用于:

- **CLI 工具**:即时启动事项所在的指挥线公用设施
- **微观服务**: 小多克图像, 库伯涅茨更快的缩放
- **无服务器/Lambda**: 寒冷的启动时间直接影响到您的账单
- **边缘装置**:草莓皮、资源有限的IoT装置
- **网关应用程序**:反向代理,API网关,其吞吐量高
- **桌面工具**: 在没有“安装 .NET 第一步”的情况下, 装运单.exe 。

跳过 AOT 用于 :

- 启动时间不重要的传统网络应用程序
- 使用大量迁移的重实体框架应用程序
- 动态装入 DLLLS 的插件系统
- 运行时生成代码的任何信息

对于 CLI 工具、 微服务、 容器和边缘设备来说, 改进是改变游戏的。 但是当您添加数据库时, 特别是 SQLite , 就会发现问题。

## SQLite 问题

现在你们明白AOT是什么了,让我们来谈谈一个最大的疼痛点: SQLite。这是在试图使用AOT使用数据库支持的应用程序时被大多数人击中的墙壁。

### 发生什么(挫折经历)

以下是典型的旅程:

1. 您添加 `Microsoft.Data.Sqlite` 对您的工程项目
2. 您配置 `PublishAot=true` 在您 `.csproj`
3. 建筑完整,没有错误——一切都看起来不错!
4. 您运行可执行文件, 并立即获得 :

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

你的应用程序在它能做任何事情之前就崩溃了 什么能给什么?

### 了解土著图书馆(快速通道)

要理解问题,你需要知道 **本地图书馆**.

SQLite不是在.NET里写的——它用C写。 它被编译为平台特有的本地代码:

- `e_sqlite3.dll` 在 Windows 上
- `libe_sqlite3.so` 关于Linux的
- `libe_sqlite3.dylib` 在 macOS 上

使用时 `Microsoft.Data.Sqlite` 在正常的.NET中,它只是围绕这个本地的 SQLite 库的包装纸。在运行时,它试图 **动态动态负载** 您平台的适当本地文件 。

使用正常的.NET, 因为:

1. 运行时间可以动态装入图书馆
2. NuGet 软件包可以包括所有平台的本地图书馆
3. 右方在运行时被选中

### 为什么AOT打破这个

原住民AOT有两个与SQLite方法相冲突的特点:

**1. 侵略性修剪**: AOT 删除了它认为你没有使用的代码。如果它不能静态地证明你需要的东西,它就会被删除。动态图书馆的加载会混淆三端- 它看不到您的代码与本地 SQLite DLL 之间的联系 。

**2. 没有动态装载支持**AOT 生成一个自成一体的二进制。它期望所有本地属地在编译时明确连接,而不是在运行时动态装入。

结果: `Microsoft.Data.Sqlite` 希望能在运行时找到本地的 SQLite 图书馆,

### 过渡依赖性噩梦

更糟糕的是,如果您有其他使用 SQLite 的 NUGet 软件包( 如一些ORM 库或我的 `mostlylucid.ephemeral.complete` 软件包中,您可以使用 **多重不兼容 SQLite 供应商** 在你的树上,在你的树上,

每个供应商都试图以不同的方式工作:

- 人们可能会期望OS提供SQLite(仅窗口)
- 另一个可能把自己自己的SQLite捆绑起来
- 另一种可能使用不同的本地图书馆版本

AOT 编译者对使用哪一种内容感到困惑, 其结果往往包括其中任何一种内容, 更糟糕的是,

## 解决方案:使用套装

经过几个小时的挫折,我找到了解决办法: `SQLitePCLRaw.bundle_e_sqlite3`。这是一个特殊的NuGet软件包,专门设计用于与AOT合作。

在您的工程中添加这两个包 :

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

### 套装有什么用?

缩略 `bundle_e_sqlite3` 软件包不同于普通 SQLite 提供者 :

**1. 包括每个主要平台的预先编篡的本地SQLite图书馆:**

- 视窗(x64、x86、ARM64)
- Linux (x64, ARM64, 象阿尔卑斯山等以肌肉为基础的突变)
- MACOS (x64 Intel,ARM64 ARM64 苹果硅)

**2. 这些本地图书馆的包装方式由AOT汇编者理解**

捆绑标记其本地属地, 以便 AOT 编译者知道将它们包括在最后的二进制书中。 没有动态装入, 没有运行时间搜索文件 。

**3. 设计用于跨平台建筑**

一个软件包适用于所有平台。 您不需要特定平台的软件包或有条件的引用 。

### 为何这样工作

记得我们发现的两个问题吗?

**问题1: AOT 的裁剪删除了它看不到的库**

- 解决方案:捆包明确宣布其本地图书馆为建设必须包括的资产

**问题2:AOT不支持动态装载**

- 解决方案: 捆绑固定链接 SQLite 库库, 在编译时间 。

结果:当您为 Windows x64 构建 Windows x64 时,包包括 `e_sqlite3.dll`当您为Linux ARM64建造时,它包括ARM64 `libe_sqlite3.so`. 一切都只是管用。

### 关键: 初始化套装( 不要跳过这个! )

90%的人试图使用SQLite 与AOT, 包括我第一次尝试。

**正常 .NET**, SQLite捆绑在第一次使用 SQLite 时自动初始化。魔法在幕后发生,不需要做任何事情。

**与原住民AOT**,自动初始化无效。AOT 编译器无法看到自动启动代码(它看起来像未使用的代码并被剪裁),所以您 **必须手动初始化** 在您应用程序初始时的捆绑 。

这就是你需要的魔力线:

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

### What does 是什么 `Batteries.Init()` 做吗?

此方法告诉 SQLite 捆绑 :

1. 为当前平台查找正确的本地 SQLite 库
2. 装入内存
3. 将所有连接连接起来 `Microsoft.Data.Sqlite` 图书馆和本地图书馆

它叫做"电池" 因为它是"包含电池"的捆绑—— 你需要的东西都是包装在一起的。

### 将它放在哪里

将 `Batteries.Init()` 计为 **第一行第一行** 在您 `Main` 方法, 前面:

- 创建应用程序构建器
- 减轻依赖性注射
- 打开任何数据库连接
- 读取可能使用 SQLite 的配置文件

把它想象成在打开前插插插设备。 如果您在拨打 SQLite 之前尝试使用 SQLite `Init()`,你仍然会得到 `DllNotFoundException` 即使 DLL 被正确捆绑在您的应用程序中 。

### 如果你忘记了会怎样?

如果你忘了打电话 `Batteries.Init()`您的应用程序将:

1. 成功构建( 编译者不会警告您)
2. 开始运行
3. 崩溃它试图使用 SQLite 的瞬间 :

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

这令人困惑,因为 DLL **是** 在您的应用程序中捆绑—— 它只是没有初始化。 我为这个错误损失了两个小时。 不要像我一样。

## 完成工程配置

这儿有满满的 `.csproj` 配置为带有 SQLite 的多平台本地 AOT :

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

### 已解释的密钥设置( 普通英语)

让我来分解一下这些设置的每个作用:

**`PublishAot=true`**

这个主开关可以让本地用户 AOT 编译。 没有这个主开关, 您就会得到正常的. NET 行为( JIT 编译) 。 有了这个, 您就会得到超时编译的本地代码 。

**`PublishTrimmed=true` 和 `TrimMode=full`**

这些人告诉编译者删除任何您不使用的代码。 把它想象成在移动前清理车库 — — 为什么你不需要收拾东西?

- `PublishTrimmed=true` 启用修剪功能
- `TrimMode=full` 意思是"攻击性, 消除一切你所能的"

**警告警告警告警告**:它可以破解使用重反射的代码(如一些 JSON 序列器或ORMs), 因为三角体不能总是通过反射看到您正在使用什么。 我们稍后会讨论如何处理。

**`InvariantGlobalization=true`**

这将删除您应用日期格式、 货币符号、 不同语言文本排序规则中所有的文化特定数据。 保存 5- 10MB 。

仅设定此仅为 `true` 如果您的应用程序:

- 只使用英语
- 不需要文化特定日期/时间格式化
- 只使用 ordinal (byte-by-byte) 字符串比较

如果您正在建立一个 CLI 工具或 API 网关, 而不关心本地化, 这是免费的储蓄 。 如果您正在构建一个需要为法国用户格式化日期或正确排序土耳其文本的工具, 请跳过此设置 。

**`PublishSingleFile=true`**

将所有内容捆绑到一个可执行文件。 而不是有 :

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

你只得到:

```
myapp.exe
```

部署起来容易得多。

**`StripSymbols=true`**

调试符号有助于调试器在调试时显示变量名称和行号。 它们在开发过程中有用, 但会在最后的二进制书中添加数兆字节 。

此设置可以删除它们。 您的应用程序运行完全相同, 只是小一些 。

**`OptimizationPreference=Speed`**

这告诉汇编者在做决定时要优先考虑什么:

- `Speed`: 快速化( 稍大一些, 但表现更好 )
- `Size`:使其小( 稍慢, 但最小二进制大小 )

对于大多数应用, `Speed` 这是正确的选择。大小差异通常只有2-3MB,但性能差异是明显的。

**`RuntimeIdentifiers`**

这声明了您想要支持的平台。 它不完全构建这些平台, 它只是用“这些是有效的目标”来描述工具。

可用标识符 :

- `win-x64`: Windows 64 位(英特/AMD)
- `win-arm64`: Windows ARM64 (紫色Pro X等)
- `linux-x64`Linux 64比特(Ubuntu、Debian、RHEL等)
- `linux-arm64`Linux ARM64 (草莓Pi 4/5, AWS Gravton)
- `osx-x64`: MacOS 英特尔(老年麦克斯)
- `osx-arm64`M1/M2/M3 MacOS 苹果硅(M1/M2/M3 Macs)

你一次用一个平台构建一个平台 `dotnet publish -r linux-x64`例如。

## 多平台大楼

记得我说过AOT需要特定平台的建筑吗? 您需要为 Windows、 Linux x64、 Linux ARM64、 macOS Intel 和 macOS Apple Silicon 分别编集。 手动操作是乏味的 — — 但我们可以将它自动化。

### 什么是GitHub行动?

如果您不熟悉, GitHub Action 是 GitHub 所建的免费的 CI/ CD (持续整合/持续部署) 服务。 它允许您在按下代码或创建释放标记时执行自动任务 。

把它想象成拥有一个建筑服务器:

1. 观察您的 GitHub 仓库
2. 当您按下标签时 `v1.0.0`
3. 它自动旋转到 Windows, Linux, 和 MacOS 虚拟机器
4. 平行构建所有平台的应用程序
5. 创建 GitHub 版本,并附加所有二进制文件

GitHub的服务器上都有这些——你不需要维护任何基础设施。对于开放源码项目和小型个人项目来说,这是完全免费的。

### 完整的建设工作流程

以下是一个GitHub Action 工作流程, 自动为所有主要平台建立 :

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

### 如何实现这一工作流程(一步一步一步)

如果YAML看起来吓人, 这就是它用普通英语做的:

**1. 触发(`on: push: tags`)**

当您按以 Git 标记开头的 Git 标记时,工作流程会运行 `v` (类似) `v1.0.0`, `v2.3.1`这是标记发布版本的标准方式。

**2. 矩阵战略**

这是聪明的部分。 我们定义的不是写5个不同的工作流程,而是 **矩阵矩阵表** 建筑:

- 每栋建筑都有不同的 `os` (运行机机)和 `runtime` (目标平台)
- GitHub Action 运行所有五个建筑 **平行平行**
- 每一人生产一件艺术品(编集的二进制)

所以当你推 `v1.0.0`GitHub同时:

- 为建造Linux x64 和 ARM64 旋转一个 Ubuntu 机器
- 旋转一个用于构建 Windows x64 的 Windows 机器
- 制造macOS x64 和 ARM64 的macOS 机器

**3. 每项建设中的步骤**

每个平台的构建都采取同样的步骤:

1. **退出代码码**:从仓库获取您的源代码
2. **设置.NET**:安装.NET 9SDK
3. **安装ARM64 工具** 安装交叉编译工具
4. **出版出版**:运行 `dotnet publish` 该特定平台的 AOT 旗帜与 AOT 旗帜
5. **上传工艺品**:保存汇编的二进制,以便下一个工作能够访问它

**4. 结果**

全部构建完成后, 您有五个手工艺品( 二进制) 准备分发。 您可以从“ 动作运行” 下载它们, 或者用第二个工作来自动创建 GitHub 释放( 未在此片段显示, 但很容易添加) 。

### 关键:土著图书馆没有被淹没

有件事让我迷惑了好几个小时: **`PublishSingleFile=true` 只有捆绑.NET 代码**SQLite 的土著图书馆 `e_sqlite3.dll` 保持单独。

这就是为什么“谷物分配包”步骤如此重要的原因:

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

缩略 `2>/dev/null || true` 部分表示“ 如果没有符合此模式的文件, 不要失败—— 继续。 ” 这让相同的脚本在所有平台上工作 。

**你们所分配的是什么?**

- 窗口 : `myapp.exe` + `e_sqlite3.dll` (混合在ZIP中)
- 莱努: `myapp` + `libe_sqlite3.so` (混合在焦油gz中)
- 马科斯: `myapp` + `libe_sqlite3.dylib` (混合在焦油gz中)

用户提取归档并运行可执行文件。 本地的 SQLite 库坐在可执行文件旁边, 并被包在运行时自动找到 。

### 为什么手动触发手册有用

通知通知 `workflow_dispatch` 顶部触发器 :

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

这样您就可以从 GitHub 的网络界面手动启动触发, 而不按标签 。 可用于测试工作流程或创建 beta 构建 。

### ARM64 交叉合并

Linux ARM64 构建需要特别关注。 您需要交叉编译工具, 并且必须指定正确的 `objcopy` 工具 :

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

没有 `ObjCopyName` 参数,链接失败,在未经确认的文件格式上出现加密错误。

## 现实世界成果

这是我制作的YARP的机器人检测网关 中间软件和SQLite 记录。这是一个真正的项目,你可以 [从 GitHub 下载](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console).

### 二进制大小( 取自实际的 GitHub 释放量)

这些是 **实际文件大小** 而非理论估计:


|----------|-----------|-------------------|-------------------|
视窗 x64  9.2MB +1.7MB **10.9MMB** (ZIP)
Linux x64  10.8MB +1.6MB **12.4MB 12.4MB** (tar.gz) 
Linux ARM64  9.9MB +1.5MB **11.4MB 11.4MB** (tar.gz) 
MACOS 英特尔 112.MB +1.8MB + 1.8MB * **13.00MB** (tar.gz) 
-=YTET -伊甸园字幕组=- 翻译: **11.5MB :11.5MMB** (tar.gz) 

相比之下,自足型.NET 9部署 **每个平台130-150MB**我们谈论的是 **规模减少10-12x**.

细目:

- 主可执行文件: 您编译的代码 +. NET 运行时间( AOT- 编译)
- 本地 DLL: SQLite 数据库引擎( 以 C 撰写)

这两个文件必须一起分发, 但是它们仍然比传统的.NET部署要小得多。

### 启动启动性性能

温和的 Linux VPS 开始冷( 第一次请求服务) :

- **自足.NET**:~800米
- **AOT 土著AOT**:~150米
- **改进**:81%更快

此事对以下各方面有很大影响:

- 无服务器/Lambda 函数( 每毫秒您支付)
- 经常扩大/减少集装箱规模的集装箱
- CPLI 工具,每起每起每起都重新开始的CLI工具

### 内存使用

空闲内存( 开关运行, 没有流量 ) :

- **自足.NET**:85MB
- **AOT 土著AOT**: 42MB : 42MB
- **改进**:减少51%

负载( 1 000 个请求/ 秒) :

- **自足.NET**~320MB
- **AOT 土著AOT**:~1800MB
- **改进**:减少44%

较低内存是指:

- 每个东道主更多集装箱
- 低廉的云 管理帐单
- 资源有限装置的可行性(草莓皮、IoT)

## 共同点

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

这是 # 1 错误 。 即使字包正确引用, 忘记拨打 `SQLitePCL.Batteries.Init()` 在您开始的时候 `Main` 方法将引发运行时崩溃 。

**修正 :**

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

### 2. 不分配土著图书馆

这是2号错误,它给了我两次。 `PublishSingleFile=true` 不将本地 SQLite DLLs 捆绑到您的可执行文件中。 您必须将其与可执行文件一同分发 。

**如果你忘记了怎么办:**

- 你的应用程序构建精细
- 它运行在您的 dev 机器上( SQLite 可能已经安装的地方) 运行正常
- 它在生产时撞撞 `DllNotFoundException`

**修正 :**

包装释放时,始终包括:

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

在您的 GitHub 动作或部署脚本中:

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

用户应该提取归档并运行可执行文件。 本地库需要在同一目录中 。

### 3. 使用 `winsqlite3` 提供者

你可能会看到一些建议被使用 `SQLitePCLRaw.provider.winsqlite3` 在 Windows 上使用OS 提供的 SQLite 。 不要。 它只在 Windows 上起作用, 需要手动初始化, 并打破跨平台构建 。 `bundle_e_sqlite3`.

### 4. 带有EF核心的特里姆警告

如果您使用实体框架核心与 SQLite , 您将得到精密警告( IL 2026, IL 3050) 。 这些通常对于 EF Core 的 SQLite 提供商来说是安全的, 但要彻底测试 :

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

最好考虑使用 Dapper 或原始 ADO. NET 与 SQLite 用于 AOT 应用程序,

### 5. 缺少视觉演播室工具(窗口)

在 Windows 上, 土著 AOT 需要视觉工作室 MSVC 的 MSVC 链接。 如果您在开发者指令提示之外建构, 您将会在 `vswhere.exe`。 GitHub Action 自动处理此操作,但对于本地建筑,则使用:

- VS 2022 的开发者命令提示
- VS 2022 的开发者 PowerShell

或者在您的构建脚本中初始化环境 :

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

## 何时使用本地 AOT

土著AOT 完美地适用于:

- **CLI 工具**:即时启动事项
- **按摩服务和容器**: 较小图像, 更快的缩放
- **边缘装置**:草莓皮、资源有限的IoT装置
- **无服务器/Lambda**: 寒冷的开始时间至关重要
- **网关/代理应用程序**:高输送量、低管理费

避免 ATO 用于 :

- 核心使用(迁移次数、复杂查询次数)
- 反射重应用
- 动态插件系统
- 需要运行时间代码生成的应用

## 快速启动检查列表

如果你读过这整篇文章, 只想得到一份核对表,

**1. 增加包件:**

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

**2. 配置您的 `.csproj` 对于 AOT :**

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

**3. 初始化 SQLite 开始于您 `Main` 方法 :**

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

**4. 建设目标平台:**

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

**5. 测试你的二进制,它应该毫无依赖性地运行!**

## 结论 结论 结论 结论 结论

让SQLite与原住民AOT合作并不明显, 但一旦你知道魔法咒语`SQLitePCLRaw.bundle_e_sqlite3` + `Batteries.Init()`这是直截了当的。回报是巨大的: 微小的二进制,即时启动, 以及有能力在不依赖运行的情况下 部署到任何平台。

对于使用 . NET 建立 CLI 工具、 网关或边缘应用程序的任何人来说, 使用 SQLite 的本地 AOT 现在是一个现实的选择。 GitHub Action 工作流程在此提供将整个多平台构建过程自动化 — — 只要标记一个发布, 你就可以完成 。

如果您是 AOT 的新人, 请先从小开始: 先转换一个简单的 CLI 工具或工具 。 适应构建过程, 学习要预知的警告, 并理解限制 。 一旦您将基本内容降低, 您就可以处理更复杂的应用程序 。

. NET 生态系统越来越对 AOT 友好。 多数现代图书馆要么在盒子外工作,要么有清晰的AOT支持文件。 未来是本地的,而且比你想的要快。

## 实际部署经验

我将AOT编程的入口 用于:

- **数字海洋流** (Linux x64) - 以系统服务运行
- **草莓Pi 5** (Linux ARM64) - 用于测试的边缘部署
- **Windows 服务器** (Windows x64) - 作为 Windows 服务运行
- **码头集装箱** - x64和ARM64两种变异

部署过程在各地都是相同的:

1. 从 GitHub 版本下载归档
2. 取出来
3. 运行可执行文件

没有安装.NET Runtime 步骤,没有依赖性地狱,没有版本冲突,只是提取和运行。

为了多克,我的 `Dockerfile` 令人尴尬的简单:

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

生成图像为 **~130MB** 传统的.NET容器为200-2500MB。

## 完整工作示例

我在这里展示的所有东西 都来自一个真实的、可生产的项目。

- **查看源代码**: [最精密的机器人检测 控制室](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- **见 GitHub Action 工作流程**: [释放.yml](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)
- **下载预建的二进制**: [GitHub 释放](https://github.com/scottgal/mostlylucid.nugetpackages/releases)

此工程是一个最小的 YARP 反向代理, 带有将签名登录到 SQLite 的机器人检测中间软件。 它显示 :

- 使用 SQLite 的原生 AOT
- 通过 GitHub Action 建立的多平台
- 土著图书馆捆绑
- CLI 论点解析
- 结构性伐木
- 优优优关闭

克隆它,研究它,使用它 作为模板 你自己的AOT项目。

## 资源资源资源 资源资源资源 资源资源 资源资源

- [官方 .NET 土著AOT文件](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [SQLitePCLLRaw 吉特Hub仓库](https://github.com/ericsink/SQLitePCL.raw)
- [工作示例:多数液化植物检测网关](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.BotDetection.Console)
- [My GitHub 行动工作流程](https://github.com/scottgal/mostlylucid.nugetpackages/blob/main/.github/workflows/console-gateway-release.yml)

现在去建造一些小的和快速的东西。