DOCUMENT STORAGE FOR .NET 10

Your documents.
Your stack.

A consistent .NET API for documents, indexed queries, and atomic writes. Run in memory, on disk, or with Redis and PostgreSQL.

Open sourceMIT licensedMade for .NET
Application dataJSON DOCUMENTS
{ }customers / alex.jsonSTORED
{
  "id": "customer-1",
  "name": "Alex",
  "active": true
}
ILibrarianDatabase
MemoryIN PROCESS
FileSystemLOCAL JSON
RedisSHARED STORAGE
PostgreSQLPERSISTENT SQL
ASYNC · TYPED · INDEXEDLIBRARIAN
Async document operationsTyped repositoriesIndexed queriesAtomic batches
FLEXIBLE STORAGE

Storage that fits
your application.

Use the storage your workload needs.
Keep a consistent API as your application grows.

Compare persistence and query behavior
DEVELOPER EXPERIENCE

From setup to
your first document.

Get a container, add a document, read it back. Async operations and dependency injection fit naturally into your .NET application.

Your first document, step by step
01

Install a provider.
Shared dependencies come with it.

TERMINAL
dotnet add package Soenneker.Librarian.Memory
PROGRAM.CS / C#
// Register your storage provider.
builder.Services.AddMemoryLibrarianDatabaseAsSingleton();

// Inject ILibrarianDatabase into your service.
var users = await database.GetContainer("users");

await users.AddItem("user-1", """{"name":"Alex"}""");

var json = await users.GetItem("user-1");
BUILT FOR .NET

The pieces your
data layer needs.

{ T }

Work with your types.

Typed repositories handle serialization and document operations. Register source-generated JSON contracts for trim-safe, Native AOT compatible models.

Native AOT guide ↗
[ → ]

Find more. Scan less.

Filter, order, count, and page with LINQ or explicit async index methods. Supported LINQ queries create indexes automatically and maintain them on writes.

Query capabilities ↗
( ✓ )

Keep changes together.

Apply related writes across containers in one atomic batch. Add conditions to detect conflicting changes before any writes are applied.

Atomic batch guide ↗
PERFORMANCE / ENGINE COMPARISON

See how Librarian
compares.

Librarian, LiteDB, and sqlite-net running the same queries against 100,000 documents. All use in-memory storage with warm indexes.

Methodology & benchmark source ↗
MEDIAN QUERY TIMEMicroseconds (µs) · Lower is better
Median query time in microseconds. Lower is better.
QueryLibrarianLiteDB 5.0.21sqlite-net 1.11.285
Equality · 10 documents8.98329.34111.866
Range page · 10 documents10.33741.15913.113
Count · 10 matches2.01329.9253.574
Exists · 50% hits2.22822.1592.537
First match2.98918.1346.204
Skip 90,000 · take 108.64453,694.2831,240.940
Compare managed allocations Bytes per operation
Managed bytes allocated per operation. Lower is better. SQLite native allocations are excluded.
QueryLibrarianLiteDBsqlite-net
Equality · 10 documents6,84063,4409,064
Range page · 10 documents8,66589,56311,801
Count · 10 matches1,60065,0062,176
Exists · 50% hits1,60044,621344
First match1,89643,8874,864
Skip 90,000 · take 106,944189,235,8728,912

Managed allocations on the executing thread only. SQLite native allocations are excluded. sqlite-net allocates fewer managed bytes for the existence check.

Medians over seven rounds on an AMD Ryzen Threadripper PRO 9995WX, Windows 10.0.26200, .NET 10.0.12 x64. Single-threaded execution, tiered compilation disabled. Includes query construction, execution, and materialization.

Warm reads only; excludes index construction, writes, persistence, and concurrency. LiteDB uses native expression queries; sqlite-net uses its synchronous expression API and SQL SELECT EXISTS for existence checks. Deep paging includes each engine’s offset traversal strategy. Results vary by workload and hardware.

Inspect the raw results ↗
OPEN SOURCE. READY TO BUILD.

Build your next
data layer.