You've already forked Extensions.Configuration.EntityFrameworkCore
Working implementation
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# 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<MyDbContext> options) : DbContext(options), ISettingsDbContext<DbSet<Setting>, Setting>
|
||||
{
|
||||
public DbSet<Setting> Settings { get; set; }
|
||||
}
|
||||
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args); // or WebApplication.CreateBuilder(args);
|
||||
|
||||
// build an initial configuration
|
||||
builder.Configuration.AddJsonFile("appsettings.json")
|
||||
...
|
||||
.AddUserSecrets<Program>(); // or wherever your connection string lives
|
||||
|
||||
builder.Configuration.AddDbSet<MyDbContext, Setting>(dbContextOptions => dbContextOptions.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
||||
|
||||
...
|
||||
// Add the PostgreSQLNotificationConfigurationReloader background service and supporting services to obtain setting reloading functionalty
|
||||
builder.Services.AddPostgreSQLNotificationConfigurationReloadService<SettingsDbContext>(); // 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.
|
||||
Reference in New Issue
Block a user