UsePostgres
Adds PostgreSQL database support for persisting train metadata, logs, manifests, and dead letters. Automatically migrates the database schema on startup.
Signatures
// Basic: uses default Npgsql data source settings
public static TraxEffectBuilderWithData UsePostgres(
this TraxEffectBuilder effectBuilder,
string connectionString
)
// With data source configuration: tune pool size, timeouts, multiplexing, etc.
public static TraxEffectBuilderWithData UsePostgres(
this TraxEffectBuilder effectBuilder,
string connectionString,
Action<NpgsqlDataSourceBuilder> configureDataSource
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
connectionString | string | Yes | PostgreSQL connection string (e.g., "Host=localhost;Database=trax;Username=postgres;Password=password") |
configureDataSource | Action<NpgsqlDataSourceBuilder> | No | Callback to configure the Npgsql data source builder. Invoked after Trax registers its enum mappings but before Build(). Use this to tune connection pool settings, enable multiplexing, or configure other NpgsqlDataSourceBuilder options. |
Returns
TraxEffectBuilderWithData, a subclass of TraxEffectBuilder that unlocks data-dependent methods like AddDataContextLogging. This provides compile-time safety: methods that require a data provider are only available on the returned type.
Examples
Basic usage with default settings:
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres("Host=localhost;Database=trax;Username=postgres;Password=password")
.AddDataContextLogging()
)
);Tuning the connection pool for high-throughput deployments:
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString, dataSource =>
{
dataSource.ConnectionStringBuilder.MaxPoolSize = 50;
dataSource.ConnectionStringBuilder.MinPoolSize = 5;
dataSource.ConnectionStringBuilder.ConnectionIdleLifetime = 300;
})
)
);What It Registers
- Migrates the database schema to the latest version via
DatabaseMigrator(unless SkipMigrations was called) - Registers an
NpgsqlDataSourcesingleton with enum mappings (TrainState,FailureClass,LogLevel,ScheduleType,DeadLetterStatus,WorkQueueStatus,MisfirePolicy). The container builds it, once per service provider, and disposes it with that provider, which closes its connection pool. Two providers built from the same service collection each get their own - Registers
IDbContextFactory<PostgresContext>for creating database contexts - Registers
IDataContext(scoped) for direct database access - Enables data context logging support (for AddDataContextLogging)
- Registers the Postgres data context provider factory, resolvable as
IDataContextProviderFactory, as a non-toggleable effect
Remarks
- Returns
TraxEffectBuilderWithData, which makesAddDataContextLogging()available at compile time. Methods that don't require a data provider (likeAddJson(),SaveTrainParameters()) use generic self-type preservation and work on bothTraxEffectBuilderandTraxEffectBuilderWithData. - The migration's scripts wait at most five seconds for a table lock. A script queued behind a transaction another instance holds open would otherwise hold every write to that table on every instance behind itself. When one gives up, the migrator runs the pending scripts again, up to ten times, and then fails startup with Postgres error
55P03; start the host again once the long transaction has ended. See Writing Migrations. - The database migration runs synchronously on startup. The database server must be accessible at application start time. To skip migration (e.g., in Lambda runners), call SkipMigrations before
UsePostgres(). - Postgres cannot store the NUL character: a
textcolumn refuses\0and ajsonbcolumn refuses the\u0000escape. Every string Trax writes through this provider has each NUL replaced with U+FFFD, the Unicode replacement character, so a train whose output or failure message contains one (a byte buffer rendered as a string,int.Parsequoting its input) still records its outcome. SQLite and the in-memory provider store NUL unchanged. A column that identifies a row is the exception: a key, a foreign key, an indexed column, a work queue entry's external id or subject key. It is compared exactly, so replacing its NUL would turn it into a different key; Postgres refuses the write instead, andWorkQueue.Createrefuses a subject key holding a NUL before it gets that far. - If Postgres still refuses a run's row because of a value it carries (SQLSTATE
22001,22021,22P05or54000, or text the client cannot encode, such as half of a surrogate pair), the data context raisesStoreRefusedContentExceptionand Trax writes the row again with the output replaced by{"_unrecorded": true}and the failure message and stack trace replaced by fixed text, keeping the state, end time, failure type, junction and class. An input refused on the run's first write is replaced the same way. The original error is logged, and it propagates only if that second write fails too. Lifecycle hooks still receive the real output and failure message. Any other failure, of the database or of another effect, propagates without a second write, so a row the database already saved in full is never overwritten with placeholders. - For lightweight deployments or local development without a database server, use UseSqlite. For tests without any persistence, use UseInMemory.
Package
dotnet add package Trax.Effect.Data.Postgres