OrionGuard.Outbox.PostgresNotify
7.0.0
dotnet add package OrionGuard.Outbox.PostgresNotify --version 7.0.0
NuGet\Install-Package OrionGuard.Outbox.PostgresNotify -Version 7.0.0
<PackageReference Include="OrionGuard.Outbox.PostgresNotify" Version="7.0.0" />
<PackageVersion Include="OrionGuard.Outbox.PostgresNotify" Version="7.0.0" />
<PackageReference Include="OrionGuard.Outbox.PostgresNotify" />
paket add OrionGuard.Outbox.PostgresNotify --version 7.0.0
#r "nuget: OrionGuard.Outbox.PostgresNotify, 7.0.0"
#:package OrionGuard.Outbox.PostgresNotify@7.0.0
#addin nuget:?package=OrionGuard.Outbox.PostgresNotify&version=7.0.0
#tool nuget:?package=OrionGuard.Outbox.PostgresNotify&version=7.0.0
OrionGuard.Outbox.PostgresNotify
Turns the outbox dispatcher from a poller into a listener on PostgreSQL: a trigger fires NOTIFY when a row is committed, and the dispatcher wakes instead of waiting out its interval.
dotnet add package OrionGuard.Outbox.PostgresNotify
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Moongazing.OrionGuard.EntityFrameworkCore;
using Moongazing.OrionGuard.Outbox.PostgresNotify;
public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options);
public static class WakeSignalSetup
{
public static void Add(IServiceCollection services, string connectionString)
{
services.AddPostgresNotifyOutboxWakeSignal(o => o.ConnectionString = connectionString);
services.AddOrionGuardEfCore<AppDbContext>(opts => opts.UseOutbox());
}
}
Install the trigger once (below), and an event committed by one process is dispatched by another within milliseconds instead of up to PollingInterval. OrionGuard.EntityFrameworkCore and Npgsql come along as dependencies.
Install the trigger
The package does not touch your schema on its own. Run the helper SQL once, from a migration:
using Microsoft.EntityFrameworkCore.Migrations;
using Moongazing.OrionGuard.Outbox.PostgresNotify;
public partial class InstallOrionGuardOutboxNotify : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.Sql(PostgresNotifyTriggerSql.Create());
protected override void Down(MigrationBuilder migrationBuilder) =>
migrationBuilder.Sql(PostgresNotifyTriggerSql.Drop());
}
Create(tableName = "OrionGuard_Outbox", channelName = "orionguard_outbox") creates (or replaces) the function orionguard_outbox_notify_<channel> and the AFTER INSERT ... FOR EACH ROW trigger orionguard_outbox_notify_trigger_<channel>, which calls pg_notify(channel, NEW."Id"::text). It is safe to run again. Drop(tableName, channelName) removes both — pass the same arguments. For a custom table or channel, pass the names to both methods and set PostgresNotifyOptions.ChannelName to the same channel.
Both names must be plain identifiers: 1 to 128 ASCII letters, digits or underscores, not starting with a digit. Anything else throws ArgumentException, because the names are spliced into DDL and into the function body — a name with a quote or a $$ in it could otherwise close the statement it sits in.
How the wake-up works
AddPostgresNotifyOutboxWakeSignal registers PostgresNotifyOutboxWakeSignal as the IOutboxWakeSignal, replacing the polling-only default whichever order you call it in, and as a hosted service.
- The hosted service opens an
NpgsqlConnectionof its own — not one from yourDbContextpool, because aLISTENconnection is parked indefinitely — runsLISTEN "orionguard_outbox";and waits. - Each notification wakes the dispatcher, as does every
SaveChanges/SaveChangesAsyncin the same process. Wake-ups coalesce: at most one is pending at a time. - PostgreSQL delivers a notification only when the inserting transaction commits, so a rolled-back insert wakes nothing.
- If the connection drops, the listener reconnects with a doubling delay. Until it is back the dispatcher falls back to
OutboxOptions.PollingInterval, which bounds latency in every case.
Options
PostgresNotifyOptions |
Default | Notes |
|---|---|---|
ConnectionString |
none | Required. Missing means the hosted service throws InvalidOperationException and the host does not start. |
ChannelName |
orionguard_outbox |
Must match the channel the trigger uses. |
InitialReconnectDelay |
1 s | First delay after a connection failure; doubles each further failure. |
MaxReconnectDelay |
30 s | Upper bound on that delay. |
Locking on PostgreSQL
This package only wakes the dispatcher; the lock is still the outbox's. The default SkipLockedDistributedLock works on PostgreSQL — it takes the lock table's name, schema and columns from your EF Core model and quotes them the way the provider does, which the mixed-case "OrionGuard_OutboxLocks" needs. Map it with OutboxLockEntityTypeConfiguration and apply the migration. A single instance can skip the table with UseDistributedLock<NullDistributedLock>(), and OrionGuard.Locks.Redis is the alternative where Redis is already at hand.
What this does not do
- It is an optimization, not a delivery mechanism. A wake-up carries no rows: the dispatcher still takes its lock and reads the table. On several replicas every listener wakes and only the lock holder dispatches. Delivery correctness rests entirely on the outbox table, which is why
PollingIntervalstill bounds the worst case. - The trigger is yours to install and keep. Nothing installs or verifies it at runtime, so a database restored without it silently degrades to polling with no error anywhere.
AFTER INSERTonly. A row updated back into the unprocessed state — a dashboard replay, say — fires nothing. That row waits for the next poll.- No schema prefix. The table name is written as a single quoted identifier, so
myschema.OrionGuard_Outboxis rejected; the table must be reachable through thesearch_pathof the session that runs the SQL. - It raises Npgsql for your whole application. The Npgsql reference flows to every project that references this package, so your
Npgsql.EntityFrameworkCore.PostgreSQLprovider must be a release that works with that Npgsql major. - Shutdown is best effort. Hosted services stop in reverse registration order, so the listener may stop before the dispatcher; the dispatcher's remaining waits then simply last the polling interval until it stops too.
Targets
net8.0, net9.0, net10.0; Npgsql 10.x. Use the OrionGuard.EntityFrameworkCore build from the same OrionGuard release.
With the rest of OrionGuard
OrionGuard.EntityFrameworkCore (the outbox) · OrionGuard.Outbox.SqlServerBroker (the SQL Server equivalent) · OrionGuard.Outbox.Dashboard · OrionGuard.Locks.Redis
Documentation
License
MIT. See LICENSE.txt.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Npgsql (>= 10.0.3)
- OrionGuard.EntityFrameworkCore (>= 7.0.0)
-
net8.0
- Npgsql (>= 10.0.3)
- OrionGuard.EntityFrameworkCore (>= 7.0.0)
-
net9.0
- Npgsql (>= 10.0.3)
- OrionGuard.EntityFrameworkCore (>= 7.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.0.0 | 90 | 9/20/2026 |
| 6.7.0 | 128 | 7/20/2026 |
| 6.6.2 | 138 | 6/20/2026 |
| 6.6.1 | 126 | 6/20/2026 |
| 6.6.0 | 128 | 6/19/2026 |
| 6.5.30 | 142 | 6/17/2026 |
| 6.5.29 | 120 | 6/15/2026 |
| 6.5.28 | 120 | 6/15/2026 |
| 6.5.27 | 118 | 6/15/2026 |
| 6.5.26 | 126 | 6/13/2026 |
| 6.5.25 | 126 | 6/13/2026 |
| 6.5.24 | 118 | 6/12/2026 |
| 6.5.23 | 128 | 6/12/2026 |
| 6.5.22 | 121 | 6/11/2026 |
| 6.5.21 | 120 | 6/11/2026 |
| 6.5.20 | 118 | 6/11/2026 |
| 6.5.19 | 119 | 6/11/2026 |
| 6.5.18 | 113 | 6/11/2026 |
| 6.5.16 | 126 | 6/11/2026 |
| 6.5.15 | 121 | 6/11/2026 |