# RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL This library enhances `RAIC.Extensions.Configuration.EntityFrameworkCore` with support for reload on update via PostgreSQL's `LISTEN`/`NOTIFY` [capabilities](https://www.postgresql.org/docs/current/sql-listen.html) as exposed via [npgsql](https://www.npgsql.org/doc/wait.html). ## Goals 1. No polling! 1. Updates happen in background via worker service (`IHostedService`) 1. Only update settings which change rather than reloading all of them ## Requirements * .NET 8 ## Gotchas * Setting values cannot be `null` (as signified by the `RequiredAttribute` on `ISetting.Value`) * Setting keys must not contain the `=` character (similar to `CommandLineConfigurationProvider` & `EnvironmentVariablesConfigurationProvider`) * Small window of opportunity for updates to be missed during reconnection process * Consider adding `Keepalive` to your conenction string (https://www.npgsql.org/doc/keepalive.html) if its not already present ## Known Issues * Not tested under load ## Configuration Options There are four properties which can be configured (cf. the `PostgreSQLNotificationConfigurationBackgroundServiceOptions` POCO) 1. `ChannelName` - the name of the notification channel to listen to for updates 1. `DebounceInterval` - the time interval to wait after a notification before signaling the `IEntityFrameworkCoreDbSetConfigurationProvider` to reload. 1. `MaxReconnectionAttempts` - Maximum number of times to retry connecting after a connection dropout (eg. PostgreSQL restart, network connectivity issue) 1. `InitialReconnectionDelay` - How long to wait after connection dropout before attempting to reconnection. ## Setup For `PostgreSQLNotificationConfigurationReloader` to work it requires triggers on the `Settings` table to be established which send notifications to the channel corresponding to that specified by the `ChannelName` option (defaults to `settings_channel`). SQL similar to below is one (but not only) way this can be acheived: ```sql CREATE OR REPLACE FUNCTION notify_setting_change() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('settings_channel', concat(NEW.key,'=',NEW.value)); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION notify_setting_remove() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('settings_channel', OLD.key); RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER setting_insert_trigger AFTER INSERT ON settings FOR EACH ROW EXECUTE FUNCTION notify_setting_change(); CREATE TRIGGER setting_update_trigger AFTER UPDATE ON settings FOR EACH ROW WHEN (NEW.value <> OLD.value) EXECUTE FUNCTION notify_setting_change(); CREATE TRIGGER setting_key_update_trigger AFTER UPDATE ON settings FOR EACH ROW WHEN (NEW.key <> OLD.key) EXECUTE FUNCTION notify_setting_remove(); CREATE TRIGGER setting_delete_trigger AFTER DELETE ON settings FOR EACH ROW EXECUTE FUNCTION notify_setting_remove(); ``` Reccommend adding your SQL to the migration which adds the `Settings` table/view (or a new migration if that table/view already exists). ## Usage Example ```csharp using RAIC.Extensions.Configuration.EntityFrameworkCore; using RAIC.Extensions.Configuration.EntityFrameworkCore.Extensions; using RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Extensions; public record Setting : ISetting { [Key] public required string Key { get; set; } [Required] public required string Value { get; set; } } public class MyDbContext(DbContextOptions options) : DbContext(options), ISettingsDbContext, Setting> { public DbSet Settings { get; set; } } var builder = Host.CreateApplicationBuilder(args); // or WebApplication.CreateBuilder(args); // build an initial configuration builder.Configuration.AddJsonFile("appsettings.json") ... .AddUserSecrets(); // or wherever your connection string lives builder.Configuration.AddDbSet(dbContextOptions => dbContextOptions.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); ... // Add the PostgreSQLNotificationConfigurationReloader background service and supporting services to obtain setting reloading functionalty builder.Services.AddPostgreSQLNotificationConfigurationReloadService(); // uses default settings, other overrides exist - see code docs await builder.Build().RunAsync(); // use config as normal ``` Read more about [Configuration](https://docs.microsoft.com/en-us/dotnet/core/extensions/configuration) and [Options](https://docs.microsoft.com/en-us/dotnet/core/extensions/options) on the Microsoft Docs site.