Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

AOT Setup

For AOT/trimming compatibility, create a source-generated JSON context:

[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(Address))]
[JsonSerializable(typeof(OrderLine))]
public partial class AppJsonContext : JsonSerializerContext;

Create an instance with your desired options and attach it to the store:

var ctx = new AppJsonContext(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var store = new SqliteDocumentStore(new DocumentStoreOptions
{
DatabaseProvider = new SqliteDatabaseProvider("Data Source=mydata.db"),
JsonSerializerOptions = ctx.Options,
UseReflectionFallback = false // recommended for AOT
});

If your types are spread across multiple JsonSerializerContext classes, use TypeInfoResolverChain to combine them. The chain is tried in order — the first context that knows about the requested type wins.

var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
options.TypeInfoResolverChain.Add(UserJsonContext.Default);
options.TypeInfoResolverChain.Add(OrderJsonContext.Default);
var store = new DocumentStore(new DocumentStoreOptions
{
DatabaseProvider = new SqliteDatabaseProvider("Data Source=mydata.db"),
JsonSerializerOptions = options,
UseReflectionFallback = false
});

All JsonTypeInfo<T> parameters across the entire API are optional (= null default). When omitted, type info is automatically resolved from the configured JsonSerializerOptions.TypeInfoResolver. You can configure a JsonSerializerContext once and skip passing JsonTypeInfo<T> on every call — while retaining full AOT safety.

With explicit JsonTypeInfo<T>With auto-resolution (recommended)
store.Insert(user, ctx.User)store.Insert(user)
store.Get("id", ctx.User)store.Get<User>("id")
store.Upsert(patch, ctx.User)store.Upsert(patch)
store.SetProperty("id", (User u) => u.Age, 31, ctx.User)store.SetProperty<User>("id", u => u.Age, 31)
store.RemoveProperty("id", (User u) => u.Email, ctx.User)store.RemoveProperty<User>("id", u => u.Email)

| store.Query(ctx.User) | store.Query<User>() | | store.Query<User>("sql", ctx.User, parms) | store.Query<User>("sql", parameters: parms) | | store.QueryStream<User>("sql", ctx.User, parms) | store.QueryStream<User>("sql", parameters: parms) |

// All of these are AOT-safe when ctx.Options is configured
var user = new User { Id = "alice-1", Name = "Alice", Age = 25 };
await store.Insert(user); // Id auto-generated for Guid/int/long; string Ids must be set
var fetched = await store.Get<User>(user.Id);
var all = await store.Query<User>().ToList();
await store.Upsert(new User { Id = user.Id, Name = "Alice", Age = 30 });
var results = await store.Query<User>(
"json_extract(Data, '$.age') > @minAge",
parameters: new { minAge = 30 });
await foreach (var u in store.Query<User>().ToAsyncEnumerable())
Console.WriteLine(u.Name);

By default (UseReflectionFallback = true), if no resolver is configured or the type isn’t registered, methods fall back to reflection-based serialization. This preserves backwards compatibility.

For AOT deployments, set UseReflectionFallback = false. Reflection-based serialization produces hard-to-diagnose errors under trimming and AOT. With this flag disabled, you get a clear InvalidOperationException at the point of use:

InvalidOperationException: No JsonTypeInfo registered for type 'MyApp.UnregisteredType'.
Register it in your JsonSerializerContext or pass a JsonTypeInfo<UnregisteredType> explicitly.

This tells you exactly which type is missing and what to do about it. Every type must either be registered in your JsonSerializerContext via [JsonSerializable(typeof(T))] or passed with an explicit JsonTypeInfo<T> parameter.

Strongly-typed DocumentContext serialization modes

Section titled “Strongly-typed DocumentContext serialization modes”

The strongly-typed DocumentContext (source-generated typed sets over the store) declares its document types with [Document(typeof(T))]. Each declaration chooses how that type’s JsonTypeInfo<T> is resolved via DocumentSerialization — AOT is the goal, but reflection stays available for callers who opt out:

ModeAOT-safe?When to use
DocumentSerialization.Auto (default)Yes, if a context is registeredInherit the store — uses a registered JsonSerializerContext if present, else reflection fallback. Register a context and you get AOT safety with no extra annotation.
DocumentSerialization.JsonContextYesYou own serialization. Point JsonContext = typeof(MyJsonCtx) at your JsonSerializerContext; its source-generated metadata drops into the store’s resolver chain. Recommended for AOT.
DocumentSerialization.ReflectionNoExplicit opt-out for non-AOT apps that won’t maintain a context. Convenient, not trimming-safe.
DocumentSerialization.GeneratedYesThe generator emits the metadata-mode JsonTypeInfo for you — AOT-safe with no hand-written JsonSerializerContext. Covers POCOs (parameterless ctor + settable props) of primitives, enums, nullable value types, nested objects, List<T>, and arrays; anything outside that subset raises DDB005 (use JsonContext).
[Document(typeof(User), JsonContext = typeof(AppJsonContext))] // AOT-safe — you own STJ
[Document(typeof(Product), Serialization = DocumentSerialization.Generated)] // AOT-safe — generator owns metadata
[Document(typeof(Order))] // Auto — inherits the store
[Document(typeof(AuditLog), Serialization = DocumentSerialization.Reflection)] // explicit non-AOT opt-out
public partial class AppContext : DocumentContext;

All features are fully AOT/trimming-safe:

FeatureAOT strategy
Table-per-type mappingType name resolved at registration time via TypeNameResolver.Resolve(). No runtime reflection.
Custom Id propertyProperty name extracted from expression at registration time. No MemberInfo at runtime.
GetDiffUses JsonNode / JsonElement for deep comparison. No Deserialize<object>() or reflection.
Query translationWhere predicates lower to a shared IR and emit native SQL/BSON — no Expression.Compile(). Relational OrderBy/projections push to SQL.
In-memory evaluationLiteDB/IndexedDB and client-side filters use a compile-free tree-walking interpreter instead of Expression.Compile() — no RequiresDynamicCode (IL3050).
DI extensionsThin wrapper — delegates all work to the AOT-safe core.