Skip to content

DI and Configuration

Mark Lauter edited this page Jun 2, 2026 · 7 revisions

DI and Configuration

AddDynamoDbLite registers IAmazonDynamoDB as a singleton built from a validated SQLite connection string. This page covers the registration surface, the builder, the options record, and the exception type — everything that sits between your composition root and a working DynamoDbClient.

Overview

The DI surface lives in the DynamoDbLite namespace inside the main package — there is no separate DynamoDbLite.DependencyInjection package.

  • AddDynamoDbLite — the only registration extension.
  • DynamoDbLiteOptionsBuilder — fluent builder with up-front validation.
  • DynamoDbLiteOptions — the configuration record.
  • DynamoDbLiteConfigurationException — thrown on misconfiguration.

AddDynamoDbLite

public static IServiceCollection AddDynamoDbLite(
    this IServiceCollection services,
    Action<DynamoDbLiteOptionsBuilder> configure)

One overload. configure receives a fresh builder; the extension calls Build immediately and registers the resulting DynamoDbClient as a singleton IAmazonDynamoDB via TryAddSingleton. Existing registrations are preserved.

Validation is eager. If configure does not call WithConnectionString, or the supplied connection string is invalid, Build throws DynamoDbLiteConfigurationException synchronously — at AddDynamoDbLite time, not at first resolution. This surfaces misconfiguration during host startup rather than during the first request.

using DynamoDbLite;

builder.Services.AddDynamoDbLite(o =>
    o.WithConnectionString("Data Source=myapp.db"));

Builder surface

WithConnectionString

public DynamoDbLiteOptionsBuilder WithConnectionString(string connectionString)

Validates connectionString and stores it on the builder. Throws DynamoDbLiteConfigurationException when the input is null, empty, whitespace, or fails to parse as a SQLite connection string.

WithWriteAheadLog

public DynamoDbLiteOptionsBuilder WithWriteAheadLog()

Enables SQLite Write-Ahead Logging on file-backed stores. Off by default — call this to opt in. WAL lets readers proceed while a writer holds the write lock, which materially improves reader-writer concurrency on file-backed stores.

Has no effect on in-memory stores. SQLite does not support WAL for :memory: databases and silently falls back to the memory journal mode.

WAL is persistent on the database file once enabled. Disabling later requires an explicit PRAGMA journal_mode=DELETE outside this library.

WithMaxBatchWriteItems

public DynamoDbLiteOptionsBuilder WithMaxBatchWriteItems(int maxBatchWriteItems)

Sets the maximum number of put/delete requests a single BatchWriteItemAsync call accepts. Defaults to 25 — the limit the real DynamoDB client enforces. Raise it to seed more rows per call than DynamoDB allows in production; lower it to tighten the cap. Throws DynamoDbLiteConfigurationException when the value is less than 1.

builder.Services.AddDynamoDbLite(o => o
    .WithConnectionString("Data Source=myapp.db")
    .WithMaxBatchWriteItems(100));

The cap governs only request-size validation — it does not change how writes map to SQLite or how the call batches them. See BatchWriteItemAsync for the operation itself.

WithPragma

public DynamoDbLiteOptionsBuilder WithPragma(string name, string value)

Adds a SQLite pragma applied to every connection the client opens for an operation, after the library's own pragmas (synchronous=NORMAL, temp_store=MEMORY). Repeatable — each call adds one PRAGMA name=value;. Entries run in call order, so a later call for the same pragma wins.

The client's classic use is write-lock contention on file-backed stores:

builder.Services.AddDynamoDbLite(o => o
    .WithConnectionString("Data Source=myapp.db")
    .WithWriteAheadLog()
    .WithPragma("busy_timeout", "5000"));

busy_timeout is how long, in milliseconds, a connection waits on a write lock held by another connection before failing with SQLITE_BUSY. It matters for file-backed stores under writer contention; it is inert for in-memory stores, where shared-cache conflicts (SQLITE_LOCKED_SHAREDCACHE) bypass the busy handler and the command-timeout retry serializes writers instead. Pragmas apply uniformly to both store types — the library does not filter by pragma or store type. Picking pragmas that suit your store is your call; consult the SQLite PRAGMA reference. See Concurrency for how the wait is governed per store type.

Values are validated for injection safety, because pragma values cannot be parameterized. A name must be a SQLite identifier ([A-Za-z_][A-Za-z0-9_]*) and a value must be a signed integer (5000, -16000) or a bare keyword (NORMAL, WAL, ON). Anything else — string-valued pragmas, custom functions — throws DynamoDbLiteConfigurationException; use WithConnectionInitializer for those.

WithConnectionInitializer

public DynamoDbLiteOptionsBuilder WithConnectionInitializer(Action<SqliteConnection> configure)

Sets a callback invoked on every connection the client opens for an operation, after any WithPragma pragmas. The connection is open; run any setup the SQLite provider supports — pragmas that WithPragma won't take, custom functions, collations. This is the unrestricted escape hatch.

builder.Services.AddDynamoDbLite(o => o
    .WithConnectionString("Data Source=myapp.db")
    .WithConnectionInitializer(conn =>
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = "PRAGMA busy_timeout=5000;";
        cmd.ExecuteNonQuery();
    }));

SQLite executes synchronously under Microsoft.Data.Sqlite, so synchronous command execution in the callback is fine. The callback runs only on per-operation connections, not on the transient connections used for schema creation, the one-time WAL enable, or the in-memory keep-alive. Because it runs on each pooled open, keep it cheap. Calling this more than once replaces the previous callback; throws ArgumentNullException on a null callback.

Per-connection application order is: library defaults → WithPragma pragmas → this callback. The callback runs last, so it has the final say.

Build

Build is internal — AddDynamoDbLite is the only caller. If WithConnectionString was not called before Build, it throws DynamoDbLiteConfigurationException with the message:

Connection string was not configured. Call WithConnectionString before Build.

Options surface

public sealed record DynamoDbLiteOptions(string ConnectionString, bool UseWriteAheadLog = false)
{
    public int MaxBatchWriteItems { get; init; } = 25;
    public IReadOnlyList<KeyValuePair<string, string>> Pragmas { get; init; } = [];
    public Action<SqliteConnection>? ConnectionInitializer { get; init; }
}

A positional record with one required parameter, one optional flag, and three optional init-only members. The positional constructor is unchanged, so existing new DynamoDbLiteOptions(...) calls keep working.

  • ConnectionString — required. No default; consumers opt in to either an in-memory or file-based store. See Getting Started for the connection string forms and the in-memory naming foot-gun.
  • UseWriteAheadLog — optional, defaults to false. Enables SQLite Write-Ahead Logging on file-backed stores; no effect on in-memory stores. The builder equivalent is WithWriteAheadLog — same contract, including the note that WAL is persistent on the file once enabled.
  • MaxBatchWriteItems — optional, defaults to 25. Caps the put/delete requests a single BatchWriteItemAsync accepts, matching DynamoDB's limit; raise it to exceed that limit in tests, or lower it to tighten the cap. The builder equivalent is WithMaxBatchWriteItems, which additionally rejects values below 1. Direct construction with a non-positive value is not re-validated — a value of 0 or less rejects every batch.
  • Pragmas — optional, defaults to empty. Per-connection pragmas applied after the library's own and before ConnectionInitializer; same injection-safe validation as the builder. The builder equivalent is WithPragma.
  • ConnectionInitializer — optional, defaults to null. Callback run on each operational connection after Pragmas. The builder equivalent is WithConnectionInitializer.

Setting Pragmas or ConnectionInitializer directly via the record is equivalent to the builder calls; the builder simply accumulates them. Direct construction skips the builder's eager validation, but Pragmas are re-validated when the client is constructed, so a malformed pragma still throws DynamoDbLiteConfigurationException at that point.

Connection string normalization

The connection string follows Microsoft.Data.Sqlite connection string format. The library applies ForeignKeys=true when you leave it unset and otherwise uses the string as written — Mode and Pooling are preserved. Honoring Mode is what lets Mode=Memory select an in-memory store instead of being rewritten to a file.

DynamoDbLiteConfigurationException

public sealed class DynamoDbLiteConfigurationException : Exception

Thrown by WithConnectionString and Build on the conditions documented above. Carries the standard Exception shape — message, inner exception when wrapping a SqliteConnectionStringBuilder parse failure, no extra state.

Catch this type at composition-root level to surface configuration problems with a clear message; do not catch it at request time, since misconfiguration has already crashed startup.

Direct instantiation

DynamoDbClient accepts the same DynamoDbLiteOptions record without DI:

using var client = new DynamoDbClient(new DynamoDbLiteOptions(
    "Data Source=myapp.db"));

See Getting Started for the full direct-instantiation walkthrough including in-memory naming.

IConfiguration

IConfiguration-based registration is not implemented today. Bind your config to a string and pass it through WithConnectionString:

builder.Services.AddDynamoDbLite(o =>
    o.WithConnectionString(builder.Configuration["DynamoDbLite:ConnectionString"]
        ?? throw new InvalidOperationException("DynamoDbLite:ConnectionString missing")));

Next steps

  • Getting Started — installation and the full setup walkthrough
  • Performance — when to use WithWriteAheadLog and WithPragma, and which knobs move write throughput
  • FAQ — connection string and configuration gotchas
  • API Parity — what IAmazonDynamoDB surface is supported

Clone this wiki locally