Example

A support-ticket system, written once.

Declare storage intent once. Move backends without touching the domain contract.

In code

A manifest that travels across providers.

One declaration; the same indexes, concurrency, and serialization across every supported backend.

SupportTicketStorage.cs
C#
1// Declare storage intent once — portable across providers.
2public sealed class SupportTicketStorage : StorageManifest<SupportTicket>
3{
4 public SupportTicketStorage() : base("support_tickets")
5 {
6 Serialization.UseJson();
7 Concurrency.UseOptimistic(t => t.Version);
8 
9 // Declared indexes — portable query semantics
10 Indexes.Unique(t => t.TicketNumber);
11 Indexes.On(t => t.CustomerId);
12 Indexes.On(t => t.Status);
13 Indexes.On(t => t.AssigneeId);
14 Indexes.On(t => t.Priority);
15 }
16}
Example

A support-ticket system, written once.

The same manifest carries from local SQLite through production PostgreSQL or MongoDB — without touching the domain.

  1. 01
    Declare
    Define ticket storage in one StorageManifest — fields, indexes, concurrency.
  2. 02
    Materialize locally
    Run against SQLite for development and tests with zero infrastructure.
  3. 03
    Promote backend
    Move to PostgreSQL or MongoDB without changing the domain contract.
  4. 04
    Query by index
    All access goes through declared indexes — portable across providers.
  5. 05
    Preserve concurrency
    Optimistic version tokens travel with the manifest, not the provider.
same call site, any provider
var ticket = await store.GetByIndexAsync(
    t => t.TicketNumber,
    "TCK-10472"
);

ticket.Status = TicketStatus.Resolved;

await store.UpdateAsync(ticket); // optimistic
Runs unchanged on:
SQLite PG Mongo

Build persistence on declared intent.

Use Groundwork as an MIT-licensed foundation for provider-neutral persistence in .NET applications.