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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OrionGuard.Outbox.PostgresNotify" Version="7.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OrionGuard.Outbox.PostgresNotify" Version="7.0.0" />
                    
Directory.Packages.props
<PackageReference Include="OrionGuard.Outbox.PostgresNotify" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OrionGuard.Outbox.PostgresNotify --version 7.0.0
                    
#r "nuget: OrionGuard.Outbox.PostgresNotify, 7.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OrionGuard.Outbox.PostgresNotify@7.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OrionGuard.Outbox.PostgresNotify&version=7.0.0
                    
Install as a Cake Addin
#tool nuget:?package=OrionGuard.Outbox.PostgresNotify&version=7.0.0
                    
Install as a Cake Tool

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 NpgsqlConnection of its own — not one from your DbContext pool, because a LISTEN connection is parked indefinitely — runs LISTEN "orionguard_outbox"; and waits.
  • Each notification wakes the dispatcher, as does every SaveChanges/SaveChangesAsync in 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 PollingInterval still 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 INSERT only. 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_Outbox is rejected; the table must be reachable through the search_path of 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.PostgreSQL provider 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed