Skip to content

Native AOT

github-actions[bot] edited this page Sep 2, 2026 · 4 revisions

Native AOT

Native AOT compiles your application into a standalone native binary ahead of time, rather than running through the .NET runtime at startup. The result is faster startup, lower memory use and no JIT overhead. SQLite.Framework doesn't generate code at runtime, so the library itself is AOT-compatible. The setup below is mostly about keeping the trimmer happy on your own types.

Use the source generator

For AOT builds, add the SQLite.Framework.SourceGenerator package and call UseGeneratedMaterializers on your options builder. The generator writes the code that turns SQLite rows and projected JSON collections into .NET results at build time, so the trimmer can see every public type used in a Select. Private types and private methods still go through reflection at runtime. See the Source Generator page for the full setup.

using SQLite.Framework.Generated;

SQLiteOptions options = new SQLiteOptionsBuilder("app.db")
    .UseGeneratedMaterializers()
    .Build();

If your solution has several projects that build queries, add the package to each project that calls UseGeneratedMaterializers. The generated class is per project and is marked internal, so it cannot be shared across projects.

Without the generator, the library still works under AOT but uses reflection for each query. That is slower on first use, triggers trimmer warnings and can cause it to break in production.

Project setup

Enable AOT in your .csproj file:

<PropertyGroup>
    <PublishAot>true</PublishAot>
    <IsTrimmable>true</IsTrimmable>
</PropertyGroup>

Preserve built-in types

The AOT trimmer removes code it thinks is unused. SQLite.Framework uses reflection to read and write columns for built-in types like int, string, DateTime and so on. You need to tell the trimmer to keep all of that code.

Add a TrimmerRootDescriptor.xml file to your project and reference it from the .csproj:

<ItemGroup>
    <TrimmerRootDescriptor Include="TrimmerRootDescriptor.xml" />
</ItemGroup>

The descriptor should preserve every type that can appear as a column value:

<linker>
  <assembly fullname="System.Private.CoreLib">
    <type fullname="System.Boolean" preserve="all" />
    <type fullname="System.Byte" preserve="all" />
    <type fullname="System.SByte" preserve="all" />
    <type fullname="System.Char" preserve="all" />
    <type fullname="System.Int16" preserve="all" />
    <type fullname="System.UInt16" preserve="all" />
    <type fullname="System.Int32" preserve="all" />
    <type fullname="System.UInt32" preserve="all" />
    <type fullname="System.Int64" preserve="all" />
    <type fullname="System.UInt64" preserve="all" />
    <type fullname="System.Single" preserve="all" />
    <type fullname="System.Double" preserve="all" />
    <type fullname="System.Decimal" preserve="all" />
    <type fullname="System.String" preserve="all" />
    <type fullname="System.DateTime" preserve="all" />
    <type fullname="System.DateTimeOffset" preserve="all" />
    <type fullname="System.DateOnly" preserve="all" />
    <type fullname="System.TimeOnly" preserve="all" />
    <type fullname="System.TimeSpan" preserve="all" />
    <type fullname="System.Guid" preserve="all" />
  </assembly>
</linker>

This covers all the types listed in Data Types. If you only use a subset of them you can trim this list down, but keeping all of them is safe and simple.

LINQ queries with anonymous types

When you write a query that projects into an anonymous type or uses object initialisers inside a select, the C# compiler generates calls to Expression.New and Expression.Bind. These methods are annotated with [RequiresUnreferencedCode], which will produce AOT warnings at publish time.

For example:

var result = await db.Table<Product>()
    .Select(p => new { p.Id, p.Name })
    .ToListAsync();

Because the types involved are directly referenced in your code, the trimmer will not actually remove them, so the warning is safe to suppress. Add [UnconditionalSuppressMessage] to any method that contains queries like this:

using System.Diagnostics.CodeAnalysis;

[UnconditionalSuppressMessage("AOT", "IL2026",
    Justification = "All types used in expression trees are referenced directly and will not be trimmed.")]
private static async Task MyQueryMethod(SQLiteDatabase db)
{
    var result = await db.Table<Product>()
        .Select(p => new { p.Id, p.Name })
        .ToListAsync();
}

You only need this attribute on methods that project into anonymous types or use object initialisers inside a select. Queries that return entity types directly do not need it.

Clone this wiki locally