Collection API
A typed NoSQL document API built on top of CSharpDB's storage engine. Store, index, and query C# objects with zero SQL.
Getting Started
Collections serialize C# objects to JSON and store them in B+trees. Each document is keyed by a string identifier, and GetCollectionAsync<T>("name") opens or creates the collection.
public record Customer(
string Name,
string Email,
Address Address,
string[] Tags
);
public record Address(string City, string State, string Zip);
await using var db = await Database.OpenAsync("myapp.db");
var customers = await db.GetCollectionAsync<Customer>("customers");
Trimming and NativeAOT
The examples on this page use GetCollectionAsync<T>, which uses reflection-based JSON serialization and member binding. This API carries trimming and dynamic-code warnings; these examples should not be treated as NativeAOT-ready without changes.
The current source also provides GetGeneratedCollectionAsync<T> for a trim-safe typed collection. It requires a generated or manually registered ICollectionModel<T> and throws if no model is registered. The generated route uses [CollectionModel(typeof(YourJsonSerializerContext))] with the collection generator and a matching JSON serializer context. See the generated collection smoke project for the complete model and project setup, or use the SQL API for NativeAOT scenarios.
CRUD Operations
// Put (insert or update)
await customers.PutAsync("customer:alice", new Customer(
"Alice", "alice@example.com",
new("Portland", "OR", "97201"),
["premium", "early-adopter"]
));
// Get by key
var alice = await customers.GetAsync("customer:alice");
// Delete
await customers.DeleteAsync("customer:alice");
// Scan all documents
await foreach (var entry in customers.ScanAsync())
Console.WriteLine($"{entry.Key}: {entry.Value.Name}");
Indexing
Collections support several indexing strategies for efficient queries without full scans.
Scalar Member Index
Index a top-level property for exact-match and range queries.
await customers.EnsureIndexAsync(c => c.Email);
await foreach (var match in customers.FindByIndexAsync(c => c.Email, "alice@example.com"))
Console.WriteLine(match.Key);
Nested Object Path
Index into nested objects using property path expressions like $.address.city.
await customers.EnsureIndexAsync("Address.City");
await foreach (var match in customers.FindByPathAsync("Address.City", "Portland"))
Console.WriteLine(match.Key);
Array Element Path
Index into terminal array elements for membership queries like $.tags[].
// Index array elements — one index entry per tag per document
await customers.EnsureIndexAsync("$.tags[]");
await foreach (var match in customers.FindByPathAsync("$.tags[]", "premium"))
Console.WriteLine(match.Key);
Range Queries
Query indexed properties with range bounds for ordered data.
public record Order(string Customer, double Total, string Date);
var orders = await db.GetCollectionAsync<Order>("orders");
await orders.EnsureIndexAsync(o => o.Total);
// Find orders with total between 100 and 500
await foreach (var match in orders.FindByPathRangeAsync(
o => o.Total, 100.0, 500.0))
{
Console.WriteLine(match.Key);
}
Index Types Summary
| Index Type | Path Pattern | Use Case |
|---|---|---|
| Scalar member | $.Name | Top-level property exact match |
| Nested object | $.Address.City | Nested property lookup |
| Terminal array | $.Tags[] | Array membership queries |
| Nested array-object | $.Orders[].Sku | Property within array elements |