Type to search pages and rules.

Browse pages

.NET Persistence

Marten is the baseline provider for persistence.

Markdown5 provisions

Intent#

Marten is the baseline provider for persistence. The shared provisions live on the stack-neutral page (Backend persistence); this page holds what the .NET stack alone requires.

The efcore extension replaces the provider-specific provisions it names for the aggregates it owns.

Standards#

Stage aggregate writes through repositories#

ruleguidance

Requirement A command handler MUST reach persistence only through a Domain repository, never through IDocumentSession.

Rationale Infrastructure implements each Domain repository with the scoped session. A Domain repository loads one aggregate type and exposes no session, generic query, or save operation. This provision realizes litespec/backend/persistence#stage-aggregate-writes-through-domain-repositories.

litespec/backend/dotnet/persistence#stage-aggregate-writes-through-repositories

Query through IQuerySession#

ruleguidance

Requirement A query handler MUST project through IQuerySession, applying filter, scope, ordering, and limit before materialization.

Rationale A read-store interface per aggregate, a generic read repository, or a callback wrapper adds a layer that the session already provides. Ordering and paging follow litespec/backend/api#bound-collection-queries. This provision realizes litespec/backend/persistence#query-through-the-read-session.

ProviderQuery surface
Marten baselineIQuerySession
EF Core extensionThe context type the extension declares

The table names the binding per provider. The obligation to project with filter, scope, ordering, and limit holds under each.

litespec/backend/dotnet/persistence#query-through-iquerysession

Commit once in the command pipeline#

ruleguidance

Requirement Exactly one global command post-handler MUST call SaveChangesAsync for a command pipeline.

Rationale A failed command then leaves the scoped session uncommitted. Handlers, repositories, validators, reactions, and endpoints never commit. This provision realizes litespec/backend/persistence#commit-each-command-pipeline-once.

The obligation is one commit for one pipeline, not one named method. Under the baseline provider that commit is IDocumentSession.SaveChangesAsync. Under the efcore extension it is DbContext.SaveChangesAsync. The pipeline wraps the call in an explicit transaction when it writes through more than one context.

litespec/backend/dotnet/persistence#commit-once-in-the-command-pipeline

Conventions#

Use this Infrastructure layout#

conventionguidance

Default Place Marten configuration, repositories, commit behavior, and serialization under one Infrastructure persistence folder.

Replacement A consumer records a departure as an Exception record naming this provision, with reason and review date.

Rationale This provision realizes litespec/backend/persistence#use-one-persistence-folder.

Example

Text
{ProjectName}.Infrastructure/
  Persistence/
    Marten/
      MartenServiceRegistration.cs
      MartenStoreConfiguration.cs
      Commands/
        SaveChangesCommandPostHandler.cs
      Events/
        DomainEventBuffer.cs
      Posts/                        single aggregate whose name matches the module: files directly under the module
        PostRepository.cs
        PostMartenConfiguration.cs
      Customers/                    two aggregates: one folder per aggregate
        CustomerAccounts/
          CustomerAccountRepository.cs
          CustomerAccountMartenConfiguration.cs
        Consents/
          ConsentRepository.cs
          ConsentMartenConfiguration.cs

The module folders follow litespec/backend/architecture#organize-every-layer-by-module-and-use-case. A single-aggregate module stays flat when its aggregate root's plural name equals the module name. For example, Post stays flat under Posts. Otherwise, each aggregate takes its own folder.

This also applies to modules with multiple aggregates. The example keeps aggregate-specific configuration beside the aggregate. The example keeps session and commit plumbing under the Marten root.

litespec/backend/dotnet/persistence#use-this-infrastructure-layout

Register one scoped session#

conventionguidance

Default Register IDocumentSession with the WebApi request or Worker operation scope.

Replacement A consumer records a departure as an Exception record naming this provision, with reason and review date.

Rationale Repositories and the commit handler in one command then resolve the same session instance. This provision realizes litespec/backend/persistence#use-one-session-per-command.

litespec/backend/dotnet/persistence#register-one-scoped-session

Reference example#

This informative example demonstrates litespec/backend/dotnet/persistence#stage-aggregate-writes-through-repositories.

C#
internal sealed class PostRepository(
    IDocumentSession session,
    DomainEventBuffer eventBuffer) : IPostRepository
{
    public async Task<Post> GetByIdAsync(
        PostId id,
        CancellationToken cancellationToken)
    {
        var post = await session.LoadAsync<Post>(id, cancellationToken);
        return post ?? throw new PostNotFoundException();
    }

    public void Store(Post post)
    {
        session.Store(post);
        eventBuffer.Track(post);
    }
}

Verification#

IDMarkEvidence
litespec/backend/dotnet/persistence#stage-aggregate-writes-through-repositoriesguidanceDoes each command handler reach persistence only through a Domain repository, never through IDocumentSession?
litespec/backend/dotnet/persistence#query-through-iquerysessionguidanceDoes each query handler project through IQuerySession, with filter, scope, ordering, and limit applied first?
litespec/backend/dotnet/persistence#commit-once-in-the-command-pipelineguidanceDoes exactly one command post-handler call SaveChangesAsync for each pipeline, with no other caller committing?
litespec/backend/dotnet/persistence#use-this-infrastructure-layoutguidanceDo Marten configuration, repositories, and commit behavior sit under the one persistence folder?
litespec/backend/dotnet/persistence#register-one-scoped-sessionguidanceDo the Domain repositories and the commit handler in one command resolve one IDocumentSession?