PgNotify.Core 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package PgNotify.Core --version 1.0.0
                    
NuGet\Install-Package PgNotify.Core -Version 1.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="PgNotify.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PgNotify.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="PgNotify.Core" />
                    
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 PgNotify.Core --version 1.0.0
                    
#r "nuget: PgNotify.Core, 1.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 PgNotify.Core@1.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=PgNotify.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=PgNotify.Core&version=1.0.0
                    
Install as a Cake Tool

PgNotify

PostgreSQL LISTEN/NOTIFY, integrated into EF Core as a first-class feature: configure it like any other EF Core concern, get trigger DDL generated by migrations automatically, and consume notifications at runtime through handlers keyed on your entities — no hand-written SQL, no manual LISTEN management, and no channel name written twice.

[NotifyChanges]
public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class UserProjection : IDatabaseUpdatedHandler<User>
{
    public Task HandleAsync(NotificationEnvelope envelope, CancellationToken cancellationToken)
    {
        // envelope.Keys["id"] — the row that changed; envelope.Changed — which columns did
        return Task.CompletedTask;
    }
}
builder.Services.AddDbContext<AppDbContext>(o => o
    .UseNpgsql(connectionString)
    .UseNpgsqlNotifications());

builder.Services.AddPostgresNotifications(o =>
{
    o.AddHandlersFromAssembly(typeof(Program).Assembly);

    // Channels and connection string both come from AppDbContext, because it opted in above.
    o.AddNotificationMappingFromDbContexts();
});

Handlers are keyed on the entity type, not on an event type: IDatabaseInsertedHandler<T>, IDatabaseUpdatedHandler<T>, IDatabaseDeletedHandler<T> for one operation, IDatabaseNotificationHandler<T> for all three, and the non-generic IDatabaseNotificationHandler for every entity — which is what a listener with no CLR entity types uses, alongside MapChannel("some_channel").

A listener with no DbContext states its channels itself (o.MapChannel<User>("User"), o.ConnectionString = ...); one that has a DbContext derives both from it and states nothing.

dotnet ef migrations add picks up the [NotifyChanges] configuration automatically and generates the trigger/trigger-function DDL alongside the usual CREATE TABLE — see docs/migrations.md for exactly what gets generated.

Why

Most PostgreSQL notification setups mean hand-writing trigger functions, remembering to update them when columns change, and hand-rolling a LISTEN connection with reconnect logic. This library treats all of that as EF Core's job: the trigger SQL is derived from the same model you already declare for your tables, regenerated automatically whenever that configuration changes, and the runtime side gives you the same DX EF Core migrations/DbSets already give you for everything else.

Features

  • Attribute or fluent configuration[NotifyChanges] or .HasDatabaseNotifications(...). Every option exists in both forms and they write identical model state, including the payload default, so moving an entity between them changes nothing about what is generated.
  • Property filteringOnUpdate(x => new { x.Name, x.Email }) only fires when those columns actually change (IS DISTINCT FROM, computed in SQL).
  • Three channel strategies — one channel per entity (default), one shared channel, or topic-style (user.created/user.updated/user.deleted) — see INotificationChannelNamingStrategy.
  • Two built-in payload shapesWithPayload(NotificationPayloadKind.Minimal | Extended), minimal by default (smallest payload, least row data leaving the database, and the key at the top level where a typed event binds it) — plus a projection, WithPayload(x => new { x.Status, x.Total }), which puts exactly those columns in the payload (the row's key always comes along) so a record OrderUpdated(int Id, string Status, decimal Total) binds without anyone having to know which payload shape the configuration picked. A fully custom INotificationPayloadBuilder is still available for anything else.
  • A payload that never breaks your writespg_notify rejects payloads over 7999 bytes by raising inside the trigger, which aborts the transaction that wrote the row. The generated trigger checks the size and sends a reduced payload (entity, operation, the key, and "truncated": true) instead, so a large row still writes and the consumer knows to re-read it. Opt out with WithPayloadOverflow(NotificationPayloadOverflow.Fail) where a reduced payload is worse than no write.
  • Deterministic, idempotent migrationsCREATE OR REPLACE FUNCTION + DROP TRIGGER IF EXISTS + CREATE TRIGGER, verified to produce an empty diff when nothing changed (see docs/migrations.md).
  • Database-first support — no dotnet ef migrations required: context.Database.EnsureNotificationTriggersAsync() applies the same idempotent trigger DDL directly against existing tables, and GenerateNotificationTriggersScript() renders it as SQL to check into Flyway/DbUp/your own tooling instead (see docs/migrations.md#database-first-without-dotnet-ef-migrations).
  • Configurable object namingWithNamePrefix(...) / [NotifyChanges(NamePrefix = ...)] / modelBuilder.HasNotificationNamePrefix(...) prefix every generated trigger/function name, so they never collide with functions/triggers you didn't generate (see docs/migrations.md#avoiding-name-collisions-with-a-custom-prefix).
  • A real runtime listener — dedicated connection, automatic reconnect with jittered exponential backoff, graceful shutdown, health check.
  • Channels derived from the modeloptions.AddNotificationMappingFromDbContexts() reads the channel names out of the same entity configuration that generates the triggers, so the two sides cannot drift; it also inherits the context's connection string, adjusted for a LISTEN connection (Multiplexing and Pooling off — both measured to matter).
  • Entity-keyed handlersIDatabaseInsertedHandler<T>/IDatabaseUpdatedHandler<T>/ IDatabaseDeletedHandler<T>/IDatabaseNotificationHandler<T>/IDatabaseNotificationHandler (multiple handlers, DI-scoped), or await foreach (var e in notifications.Events<T>()) for strongly-typed streams.
  • A middleware pipelineUseLogging() / UseRetry() / UseMetrics() / custom INotificationMiddleware.
  • IChangeToken change trackingAddChangeTracking() gives every entity an IEntityChangeTracker<T> for IMemoryCache expiration tokens and ETag/Last-Modified generation, with LastModified taken from the trigger's clock so ETags stay identical across instances (see docs/architecture.md#change-tracking-ichangetoken).
  • Roslyn analyzersPGN001PGN004 catch common misconfiguration at compile time.

See docs/architecture.md for how these fit together, including a sequence diagram of the full trigger-to-handler lifecycle.

Getting started

  1. Reference PgNotify.EFCore and PgNotify.Migrations from your data-access project, and PgNotify.Runtime (plus PgNotify.Runtime.EFCore, to derive the channels from a DbContext) from whatever process hosts the listener. Or, to avoid picking individual packages by hand, reference the two meta-packages instead: PgNotify.Writer (EFCore + Migrations + Analyzers) from the project that owns the DbContext, and PgNotify.Listener (Runtime + Runtime.EFCore) from whatever project listens — a single-process app just references both. A listener with no EF Core at all references PgNotify.Runtime alone.
  2. Configure notifications on your entities (attribute or fluent API — see above).
  3. Chain .UseNpgsqlNotifications() after .UseNpgsql(...) — both where the app builds its DbContextOptions and in your IDesignTimeDbContextFactory (for dotnet ef tooling).
  4. dotnet ef migrations add ... / dotnet ef database update (or Database.Migrate() at startup) as usual.
  5. builder.Services.AddPostgresNotifications(o => { o.AddHandlersFromAssembly(...); o.AddNotificationMappingFromDbContexts(); }); (or, with no DbContext in the process, o.ConnectionString = ... and o.MapChannel<Entity>("channel")).
  6. Consume via an IDatabase…Handler<TEntity> implementation or IPostgresNotificationService.Events<T>().

The full working example: samples/CacheInvalidation.WebApi (run it with docker compose up -d && dotnet run — see its own README). For the same problem solved with IChangeToken/ETag instead of a hand-written handler — and with no dotnet ef required, against either the bundled container or any PostgreSQL you already have — see samples/HttpCaching.WebApi. For a two-process setup — a web app with a UI that writes the data, and a completely separate console app with no EF Core dependency at all that listens for changes — see samples/TaskBoard.WebApi + samples/TaskBoard.Watcher. For a setup built on the PgNotify.Writer/PgNotify.Listener meta-packages instead of the individual library projects — a write-side API and a separate CQRS-style read-model projector, each with a single package reference — see samples/Orders.WebApi + samples/Orders.Projector.

Project layout

src/        PgNotify.Core / .EFCore / .Migrations / .Runtime / .Runtime.EFCore / .Analyzers
            PgNotify.Writer / .Listener - empty meta-packages bundling the above by role
tests/      One test project per src/ project, plus PgNotify.IntegrationTests (Testcontainers)
samples/    CacheInvalidation.WebApi, HttpCaching.WebApi, TaskBoard.*, Orders.* (full) +
            design sketches for two other scenarios
docs/       architecture.md, migrations.md, troubleshooting.md, performance.md, versioning.md

See docs/architecture.md for why each project exists.

Testing

  • Unit tests per project (Core, EFCore, Runtime), no external dependencies.
  • PgNotify.Migrations.Tests — SQL-generation snapshot-style tests: builds a real Npgsql relational pipeline and asserts on the exact generated DDL, including idempotency (an unchanged model produces zero migration operations).
  • PgNotify.IntegrationTests — real PostgreSQL via Testcontainers: applies a migration, inserts/updates/deletes through EF Core, and asserts the runtime listener receives and correctly deserializes the resulting notifications, including a reconnect scenario (pg_terminate_backend mid-test) and watched-column filtering.
  • PgNotify.Analyzers.Tests — drives the analyzer directly against real CSharpCompilations.
  • PgNotify.Runtime.EFCore.Tests — derives channel mappings from real EF Core models, against an unroutable host: reading a model needs no connection.
dotnet test                                          # everything except IntegrationTests network/docker cost
dotnet test tests/PgNotify.IntegrationTests      # requires Docker

Documentation

Supported scenarios

Schemas, composite keys, identity columns, owned-type-mapped scalar columns, JSON/XML columns (as watched/payload columns, not as a source of structural notification config; both are cast before comparison, since neither has a PostgreSQL IS DISTINCT FROM operator), and enums are all supported wherever they map to ordinary scalar columns. Unsupported/limited: navigation and collection properties can't be watched for changes (flagged by analyzer rule PGN002); table splitting and entities mapped only to a view are refused at model-build time with a descriptive error rather than supported (map the entity that needs notifications to its own table instead).

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET 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.
  • net10.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on PgNotify.Core:

Package Downloads
PgNotify.EFCore

EF Core model-building integration for PostgreSQL notifications: [NotifyChanges] attribute, HasDatabaseNotifications() fluent API, and the annotation-backed configuration model.

PgNotify.Runtime

Runtime PostgreSQL LISTEN/NOTIFY listener: dedicated connection management, automatic reconnect with backoff, a middleware dispatch pipeline, typed event streaming, DI registration, and health checks.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 137 8/11/2026
1.1.0-beta.4 62 8/11/2026
1.1.0-beta.3 65 8/11/2026
1.1.0-beta.2 65 8/10/2026
1.1.0-beta.1 59 8/10/2026
1.0.1 125 8/8/2026
1.0.0 148 8/7/2026