Groundwork

Define storage intent.Keep provider freedom.

Persistence contracts for modular .NET systems

1# Install Groundwork
2dotnet add package Groundwork
3 
4# Define once
5builder.Services
6   .AddGroundwork()
7   .AddManifest("manifest.json");
Entities01
Indexes02
Queries03
Concurrency04
[ MANIFEST ]Groundwork[ TRANSLATE ]
SQLite01
SQL Server02
PostgreSQL03
MongoDB04
  • 01 / Contract first

    Model your domain and access patterns in a single manifest. Storage intent, versioned and portable.

    manifest.json
  • 02 / Provider freedom

    Translate the same manifest to multiple providers without changing your application code.

    Translation layer
  • 03 / Modular by design

    Built for modular .NET systems with clean integration and zero provider lock-in.

    .NET 8+
  • 04 / Performance aware

    Indexes, queries, and concurrency are first-class citizens in the contract. Make performance explicit.

    Intent ⟶ Native
  • 05 / Evolvable

    Version manifests, evolve safely, and migrate with confidence across environments.

    Migrations
  • 06 / Open source

    MIT-licensed and community driven. Use it, contribute, and build the persistence layer you trust.

    Open source · MIT

Groundwork translates your manifest into provider-native storage.One contract. Many destinations.

StatusTranslation ready
01Why Groundwork

Modular .NET frameworks need provider-neutral persistence.

Extensible platforms ship as many independent modules. Each module needs to persist data — but no module should dictate the host's database stack.

Architectural pressure / 01
Storage intent belongs to the module. Storage technology belongs to the host.

Frameworks such as Elsa, Orchard Core, and ABP all show the same pattern: modules need persistence, while host applications need freedom to choose the database provider. Groundwork gives modules a contract for storage intent that travels across providers — without a hard dependency on EF Core or one engine.

  1. 01
    Modules need entities and queriesBut they should not own connection strings, schemas, or migrations.
  2. 02
    Hosts need provider freedomSQLite for dev, PostgreSQL or SQL Server for production, MongoDB when documents fit.
  3. 03
    EF Core isn't always the right couplingReusable modules shouldn't force every consumer onto one ORM or relational shape.
  4. 04
    Drift creeps in across backendsThe same shape restated in DDL, indexes, and migrations becomes inevitable rework.
02Separation

Module author declares. Host application chooses.

Groundwork draws a clean line between what a module needs to express about its data and what the host application controls about how that data is stored.

Input / module author

Declares storage intent

  • Entities and field shapes
  • Queryable fields and indexes
  • Portable query semantics
  • Optimistic concurrency rules
  • Serialization preferences
  • Lifecycle policy
  • Workload classification
[ Contract ]Groundwork
Output / host application

Chooses materialization

  • Database provider choice
  • Connection and credentials
  • Materialization timing
  • Operational and backup policy
  • Diagnostics and telemetry sinks
  • Tenant and isolation strategy
  • Optimized physicalization gates
03Concrete scenario

A module defines an entity. The host picks the database.

The same pattern shows up across workflow engines, CMS platforms, integration catalogs, multi-tenant settings, and runtime-defined business objects.

Specimen / ApprovalRule

A workflow, CMS, or application module defines an entity — an approval rule, a content metadata record, an integration catalog item, a tenant setting, or a runtime-defined business object.

It declares queryable fields like status, owner, tenant, key, or category. Groundwork validates the manifest against the configured provider and materializes the right storage shape — relational tables and indexes, or document collections and indexes — without the module knowing which.

TenantIdStatusOwnerIdVersion
ApprovalRuleStorage.csC# / MANIFEST
public sealed class ApprovalRuleStorage
    : StorageManifest<ApprovalRule>
{
    public ApprovalRuleStorage() : base("approval_rules")
    {
        Serialization.UseJson();
        Concurrency.UseOptimistic(r => r.Version);

        Indexes.On(r => r.TenantId);
        Indexes.On(r => r.Status);
        Indexes.On(r => r.OwnerId);
    }
}
04Provider choice

Same module intent. Different materialization.

A module's storage manifest stays the same. The host picks the provider that fits the deployment — and Groundwork materializes the right physical shape for it.

01 / LOCAL

SQLite

Local development, samples, integration tests, and small single-node deployments — zero infrastructure.

02 / RELATIONAL

SQL Server

Enterprise relational workloads with existing tooling, backups, and operational practice.

03 / RELATIONAL

PostgreSQL

Modern relational workloads with rich indexing and JSON support — a common default for new systems.

04 / DOCUMENT

MongoDB

Document-shaped workloads where flexible schemas and per-document indexes are the right fit.

05EF Core coexistence

Use EF Core where it fits. Use Groundwork where modules need to stay portable.

Groundwork is not a replacement for EF Core. It's a smaller persistence contract for the parts of a system where coupling every module to one ORM is the wrong trade-off.

Known relational domain

EF Core is great for

  • Application-owned relational domains the host fully controls.
  • Rich LINQ over a known relational schema.
  • Teams already standardized on EF Core migrations and tooling.
Portable module boundary

Groundwork is great for

  • Reusable modules that ship to many hosts on many database engines.
  • Runtime-defined entities that still need declared indexes and concurrency.
  • Keeping module packages light — no EF Core, no relational assumption.
The two can live in the same application. EF Core handles the host's core domain; Groundwork handles modules that need to remain provider-neutral. Nothing forces a choice between them.
06How it works

Three steps from intent to portable storage.

Every transition is explicit: declare, validate, then materialize. Nothing silently changes a database behind your back.

Storage intent

Describe units, indexes, query capabilities, and concurrency through a provider-neutral StorageManifest.

Capabilities & materialization

Providers report capabilities. Plans surface gaps and history before anything touches a database.

Portable document contracts

Application code uses one document-store contract across SQLite, SQL Server, PostgreSQL, and MongoDB.

07Features

Built for clean persistence boundaries.

Everything is declared, inspectable, and provider-aware — without surrendering provider freedom.

F.01

Provider-neutral storage manifests

Declare units, fields, and contracts once — independent of any database engine.

F.02

Declared indexes & portable queries

Query semantics travel with the manifest. Unindexed portable queries fail clearly.

F.03

Optimistic concurrency

Version tokens are first-class — surfaced consistently across providers.

F.04

Provider capability validation

Each provider reports what it supports. Manifests are checked before execution.

F.05

Materialization & schema history

Plans are explicit, inspectable artifacts — never silent runtime side effects.

F.06

Portable document-store contract

One contract for document access across relational and document backends.

F.07

Four first-party providers

SQLite, SQL Server, PostgreSQL, and MongoDB ship as discrete provider packages.

F.08

Opt-in optimized physicalization

Promote hot indexed paths to provider-native shapes, benchmark-gated and explicit.

08In code

A manifest that travels across providers.

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

SupportTicketStorage.csC# / PORTABLE
// Declare storage intent once — portable across providers.
public sealed class SupportTicketStorage : StorageManifest<SupportTicket>
{
    public SupportTicketStorage() : base("support_tickets")
    {
        Serialization.UseJson();
        Concurrency.UseOptimistic(t => t.Version);

        // Declared indexes — portable query semantics
        Indexes.Unique(t => t.TicketNumber);
        Indexes.On(t => t.CustomerId);
        Indexes.On(t => t.Status);
        Indexes.On(t => t.AssigneeId);
        Indexes.On(t => t.Priority);
    }
}
09Providers

Four first-party providers. Equal treatment, honest differences.

Each provider package translates manifests into its native shape — and reports honestly about its capabilities.

CapabilitySQLiteSQL ServerPostgreSQLMongoDB
Portable document storageSingle contract across relational and document backends.Native
Declared indexesIndex intent declared in the manifest; enforced per provider.
Materialization & historyPlans and schema history are first-class, inspectable artifacts.
Optimized physicalizationOpt-in promotion of hot paths to provider-native shapes.
10Example

A support-ticket system, written once.

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

01
DeclareDefine ticket storage in one StorageManifest — fields, indexes, concurrency.
02
Materialize locallyRun against SQLite for development and tests with zero infrastructure.
03
Promote backendMove to PostgreSQL or MongoDB without changing the domain contract.
04
Query by indexAll access goes through declared indexes — portable across providers.
05
Preserve concurrencyOptimistic version tokens travel with the manifest, not the provider.
Same call siteANY PROVIDER
var ticket = await store.GetByIndexAsync(
    t => t.TicketNumber,
    "TCK-10472"
);

ticket.Status = TicketStatus.Resolved;

await store.UpdateAsync(ticket); // optimistic

Runs unchanged on: SQLite / PostgreSQL / MongoDB

11Architecture

Principles that shape every package.

Groundwork's value comes from what it refuses to do as much as what it offers.

P.01Generic packages stay application-agnosticCore Groundwork assemblies never know about your domain. They define the contract, not the content.
P.02Provider-specific shape lives in providersApplication modules never reach for SQL or document syntax. Provider packages own physical translation.
P.03Unindexed portable queries fail clearlySurprises are worse than errors. Portable queries refuse to silently degrade.
P.04Runtime hot paths stay benchmark-gatedOptimized physicalization is explicit, measured, and opt-in — never accidental.
P.05Application integrations remain opt-inGroundwork integrates where you choose it to. It does not colonize the rest of your stack.
12Open source

Built in the open, designed for adoption.

Groundwork is open source and MIT licensed, so teams can inspect the implementation, extend providers, fork when needed, and embed it in their own .NET systems without vendor lock-in.

Inspectable

Read the implementation. No hidden runtime.

Forkable

Adapt or extend providers when your stack needs it.

MIT licensed

Embed it in commercial systems without lock-in.

Ready / translation path open

Build persistence on declared intent.

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