Skip to content

The search box knows all the secrets -- try it!

Fisher is part of the Critter Stack ecosystem.

JasperFx Logo JasperFx provides formal support for Fisher and other Critter Stack libraries. Please check our Support Plans for more details.

Fisher as Document DB

Fisher lets you use SQLite as a document database. Documents are stored as JSON text, with each document type getting its own table (prefixed fi_doc_).

Key Concepts

  • Documents are plain .NET objects serialized to JSON
  • Sessions provide a unit of work for batching changes
  • LINQ queries translate to SQLite json_extract expressions
  • Automatic schema management creates and migrates tables as needed

Document Tables

Each document type gets a table. The core columns:

ColumnTypeDescription
idVariesPrimary key — Guid, string, int or long
dataTEXTThe serialized document, exactly as the serializer wrote it
doc_typeTEXTSub-class discriminator, for a hierarchy
dotnet_typeTEXTAssembly-qualified .NET type name
last_modifiedTEXTISO-8601 UTC, written by SQLite
tenant_idTEXTPresent under conjoined tenancy

Further columns appear when a feature asks for them: guid_version or revision (concurrency), is_deleted / deleted_at (soft delete), the opt-in metadata columns, and a generated column per duplicated field.

TIP

A document table is created on demand at first write, as well as by the migration. A document type can be stored without ever being registered, and a snapshot type is registered by projection configuration — either way the first write may be the first time the table is needed.

The exception is an enlisted session, where on-demand creation would deadlock against the caller's own write lock. There a missing table throws by name.

Quick Example

cs
// Define a document
public class User
{
    public Guid Id { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
}

// Store a document
await using var session = store.LightweightSession();
var user = new User { FirstName = "Jane", LastName = "Doe", Email = "jane@example.com" };
session.Store(user);
await session.SaveChangesAsync();

// Load by id
var loaded = await session.LoadAsync<User>(user.Id);

// Query with LINQ
var users = await session.Query<User>()
    .Where(x => x.LastName == "Doe")
    .ToListAsync();

What is here

Topic
Document IdentityGuid, string, int, long and strong-typed wrappers
Opening SessionsTracking modes, enlistment, SessionOptions
Storing DocumentsStore, Insert, Update, the unit of work
Deleting DocumentsHard and soft deletes, delete by criteria
QueryingLINQ, joins, grouping, paging, raw SQL, JSON reads
IndexingDuplicated fields, declared indexes, foreign keys
HierarchiesA base type and its sub-classes in one table
ConcurrencyGuid versions or numeric revisions
PatchingChanging part of a document without loading it
Bulk InsertLoading a lot of documents at once
MetadataWhat Fisher records, and mapping it back onto members
Multi-TenancyConjoined tenancy, and writing across tenants

Released under the MIT License.