Skip to content

Mise Resource Lifetimes

SteffenBlake edited this page Sep 28, 2026 · 1 revision

Resource lifetimes

Readers and writers own the commands they execute. Connection and transaction ownership depends on how you construct them. Keep each resource alive until its operations and any stream enumeration have finished.

Connection ownership

Construction Connection owner
new DbReader(config: config) Reader
new DbReader(connection: connection) Caller
new DbWriter(config: config) Writer
new DbWriter(connection: connection) Caller
Writer supplied with a transaction Caller owns the transaction's connection

A configuration-backed scope creates and opens its connection lazily. A supplied connection may be opened when needed, but Mise does not close or dispose it. Reader construction requires exactly one connection source.

await using var connection = new NpgsqlConnection(connectionString);
await using var reader = new DbReader(connection: connection);

...

Declarations dispose in reverse order, so the reader ends before the caller-owned connection.

Operation ownership

Each terminal asynchronously disposes the command it creates. Row terminals also dispose their database reader. ListAsync returns after consuming the rows; StreamAsync holds these resources until its enumerator ends or is disposed.

Exceptions from execution or disposal propagate. Returning Result<T> does not imply database exceptions are converted into failure cases.

One operation per instance

Readers and writers permit one active operation at a time. Starting another operation on the same instance throws InvalidOperationException. This includes reader methods inherited by DbWriter, active streams, and transaction completion operations.

Do not run Task.WhenAll over calls sharing one reader or writer. Use separate scopes for independent concurrent operations, or await operations sequentially when they share a transaction.

Disposing a reader while an operation is active also throws. Finish or dispose a stream's enumerator before disposing its reader. See Streaming.

Transaction disposal order

An enlisted transaction adapter must be disposed before its writer. The writer rejects disposal while that adapter remains active. With a unit of work, declare the writer first and the unit of work second:

await using var writer = new DbWriter(config: config);
await using var work = new UnitOfWork([writer.Transaction]);

...

Commit or roll back before leaving the scope. A caller-supplied provider transaction becomes adapter-owned, while its connection remains caller-owned.

Provided resources

Partie's reader Provider owns its reader around downstream query work. Its writer transaction Provider owns the command writer and disposes the transaction before the writer through the outer unit of work.

Do not dispose provided readers or writers yourself, store them for later requests, or return a deferred stream that will enumerate after their Provider scope ends. Complete database work inside the handler or the surrounding step that owns the invocation.

See Transactions, Readers, and Mise in ASP.NET Core.

Clone this wiki locally