Skip to content

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
{
ConnectionString = "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 SqliteDocumentStore(new DocumentStoreOptions
{
ConnectionString = "Data Source=mydata.db",
JsonSerializerOptions = options,
UseReflectionFallback = false
});

When a JsonSerializerContext is attached to JsonSerializerOptions, the reflection-marked overloads (without JsonTypeInfo<T>) automatically resolve type info from the configured resolver. You can configure the context once and skip passing JsonTypeInfo<T> on every call — while retaining full AOT safety.

Without resolver (explicit JsonTypeInfo<T>)With resolver (auto-resolved)
store.Set(user, ctx.User)store.Set(user)
store.Set("id", user, ctx.User)store.Set("id", user)
store.Get<User>("id", ctx.User)store.Get<User>("id")
store.GetAll<User>(ctx.User)store.GetAll<User>()
store.Upsert("id", patch, ctx.User)store.Upsert("id", 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<User>(sql, ctx.User, parms)store.Query<User>(sql, parms)
store.GetAllStream<User>(ctx.User)store.GetAllStream<User>()
store.QueryStream<User>(sql, ctx.User, parms)store.QueryStream<User>(sql, parms)
// All of these are AOT-safe when ctx.Options is configured
var id = await store.Set(new User { Name = "Alice", Age = 25 });
var user = await store.Get<User>(id);
var all = await store.GetAll<User>();
await store.Upsert("user-1", new User { Name = "Alice", Age = 30 });
var results = await store.Query<User>(
"json_extract(Data, '$.age') > @minAge",
new { minAge = 30 });
await foreach (var u in store.GetAllStream<User>())
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.