-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This page takes you from an empty project to a working document store on SQLite: declare a manifest, open a store, save a document, and run a declared query. Everything here transfers unchanged to PostgreSQL, SQL Server, and MongoDB — only the factory call differs (see Opening-Stores).
dotnet add package Groundwork.SqliteThe provider package brings Groundwork.Core and Groundwork.Documents transitively. You need
the .NET SDK 10.0 or newer.
Groundwork starts with a provider-neutral StorageManifest. The manifest below declares a
support-ticket unit with string IDs, JSON content, optimistic concurrency, keyword logical
indexes, and one BoundedQueryDeclaration per read the application performs. The default
physical-storage policy synthesizes the projected columns and physical indexes those declarations
demand; the declared length bounds each string key so providers with sized index keys
(SQL Server) can materialize them:
using Groundwork.Core.Indexing;
using Groundwork.Core.Intents;
using Groundwork.Core.Manifests;
using Groundwork.Core.PhysicalStorage;
using Groundwork.Core.Queries;
const string DocumentKind = "supportTicket";
const string SchemaVersion = "1.0.0";
var equalOnly = new HashSet<PortableQueryOperation> { PortableQueryOperation.Equal };
var manifest = new StorageManifest(
new StorageManifestIdentity("support-tickets"),
new StorageManifestOwner("sample.support"),
new StorageManifestVersion(SchemaVersion),
[
StorageUnit.Create(
new StorageUnitIdentity(DocumentKind),
"Support ticket",
StorageIntent.PortableDocument(),
LifecyclePolicy.Mutable,
IdentityPolicy.StringId(),
TenancyPolicy.Global,
ConcurrencyPolicy.Optimistic(),
SerializationPolicy.Json(),
new StorageUnitPhysicalStorage(
StorageUnitProvisioningMode.Declared,
PhysicalStoragePolicy.Default(),
logicalIndexes:
[
new LogicalIndexDeclaration(
"by-ticket-number",
[new IndexField("ticketNumber")],
IndexValueKind.Keyword,
isUnique: true,
MissingValueBehavior.Excluded,
length: 128),
new LogicalIndexDeclaration(
"by-status",
[new IndexField("status")],
IndexValueKind.Keyword,
isUnique: false,
MissingValueBehavior.Excluded,
length: 128)
],
boundedQueries:
[
new BoundedQueryDeclaration(
"find-by-ticket-number",
"by-ticket-number",
equalOnly,
QuerySortSupport.None,
QueryPagingSupport.None,
BoundedQueryExecutionClass.ScaleBearing),
new BoundedQueryDeclaration(
"list-by-status",
"by-status",
equalOnly,
QuerySortSupport.Ascending,
QueryPagingSupport.Offset,
BoundedQueryExecutionClass.ScaleBearing,
supportsTotalCount: true)
]))
],
new HashSet<string> { "schema-history", "optimistic-concurrency" },
[]);Every read your application will ever perform is one of those BoundedQueryDeclaration entries.
There is nothing else to query with — that is the point. Declaring-Storage explains every
policy and declaration in detail.
Open SQLite with OpenPhysicalAsync. Runtime schema admission is inspect-only by default; opting
into safe startup auto-apply creates the pending additive schema. The bounded-query store is
created per executable route (one route per storage unit), resolved from the store's Routes:
using Groundwork.Core.Capabilities;
using Groundwork.Core.SchemaEvolution;
using Groundwork.Documents.Scoping;
using Groundwork.Documents.Store;
using Groundwork.Sqlite.Documents;
var provider = new ProviderIdentity("groundwork-sqlite", "1.0.0");
SqlitePhysicalDocumentStore store = await SqliteDocumentStoreFactory.OpenPhysicalAsync(
"Data Source=support-tickets.db",
manifest,
provider,
DocumentStoreAccess.Global,
options: new GroundworkRuntimeSchemaAdmissionOptions { AutoApplyOnStartup = true });
IBoundedDocumentStore boundedStore = SqlitePhysicalQueryRuntime.Create(
store,
manifest,
store.Routes.Single(route => route.StorageUnit.Value == DocumentKind),
provider);IDocumentStore stores JSON envelopes, so any CLR type works as long as it serializes to the
field names the manifest indexes declare. Optimistic concurrency is expressed with
ExpectedVersion:
using System.Text.Json;
using Groundwork.Documents.Store;
var ticket = new
{
ticketNumber = "TCK-1001",
customerId = "acme",
subject = "Invoice export fails",
status = "open",
openedAt = DateTimeOffset.UtcNow
};
var created = await store.SaveAsync(new SaveDocumentRequest(
DocumentKind,
ticket.ticketNumber,
SchemaVersion,
JsonSerializer.Serialize(ticket)));
if (created.Status != DocumentStoreWriteStatus.Saved)
throw new InvalidOperationException($"Ticket was not saved: {created.Status}");
var loaded = await store.LoadAsync(DocumentKind, ticket.ticketNumber);An update with a stale ExpectedVersion returns DocumentStoreWriteStatus.ConcurrencyConflict
instead of overwriting; deletes take the same guard. See Transactions-and-Unit-of-Work for
multi-document all-or-nothing writes.
IBoundedDocumentStore accepts a DocumentQuery bound to one of your declared bounded-query
identities:
using Groundwork.Documents.Store;
var query = new DocumentQuery(
DocumentKind,
"list-by-status",
[DocumentQueryClause.Of(DocumentQueryComparison.Equal("status", "open"))],
take: 25);
DocumentQueryResult page = await boundedStore.QueryAsync(query);
long total = page.TotalCount;
DocumentEnvelope? first = await boundedStore.FirstOrDefaultAsync(
query.Select(BoundedQueryResultOperation.First));
bool any = await boundedStore.AnyAsync(query.Select(BoundedQueryResultOperation.Any));Query planning validates every shape against the declaration and the provider's handlers at startup; unsupported server-side shapes fail compilation rather than falling back to an unbounded in-memory scan. Querying covers the full runtime model — richer operators, compound order, keyset continuations, and latest-per-key selection.
- Swap the provider: Opening-Stores shows the PostgreSQL, SQL Server, and MongoDB factories.
- Understand what you declared: Declaring-Storage.
- Deploy schema from CI/CD instead of startup: Schema-Evolution.
- See it all working together: Samples.
This wiki is generated from
docs/wiki/ in the main
repository and republished on every push to main — do not edit pages here; changes made
directly in the wiki will be overwritten. To propose a change, open a pull request against
docs/wiki/.
Using Groundwork
- Getting-Started
- Declaring-Storage
- Opening-Stores
- Querying
- Transactions-and-Unit-of-Work
- Storage-Scopes
- Schema-Evolution
- Identity-Generators
Providers
Beyond documents
Reference