DynamicWhere.ex
DynamicWhere.exv3.0.0·docs

Store Providers

Two packages hold rules outside the process. Both implement the same contracts and both pass the same conformance suite as the in-memory store, so swapping one for the other changes nothing about behaviour.

Redis

dotnet add package DynamicWhere.ex.Policies.Redis --version 3.0.0
var redis = await ConnectionMultiplexer.ConnectAsync(connectionString);
var store = new RedisPolicyStore(redis);

var provider = await StorePolicyProvider.CreateAsync(store, options);

DwPolicy.Configure(options, provider);
The poll is not redundant
Redis pub/sub is fire-and-forget: a subscriber that is briefly disconnected never learns it missed a message. The poll behind the watch is what bounds a dropped notification, so it stays even though the watch usually wins the race.

Entity Framework Core

dotnet add package DynamicWhere.ex.Policies.EntityFrameworkCore --version 3.0.0
var policyDbOptions = new DbContextOptionsBuilder<DwPolicyDbContext>()
    .UseNpgsql(connection, sql => sql.MigrationsAssembly("YourProject"))
    .Options;

// A new context per read: the provider polls on a background timer, and a
// context shared with request threads would be used concurrently.
var store = new EfPolicyStore(() => new DwPolicyDbContext(policyDbOptions));

var provider = await StorePolicyProvider.CreateAsync(store, options);

DwPolicy.Configure(options, provider);
MigrationsAssembly is not optional
DwPolicyDbContext is declared in the NuGet package, and EF Core looks for migrations in the assembly that declares the context. Left alone, dotnet ef migrations add refuses outright with "your target project does not match your migrations assembly", and a running application finds no migrations to apply. Point it at your own project in both places you build the context — the runtime registration and any design-time factory.

Two tables, on their own migration history:

TableHolds
DwPolicyRulesOne row per rule, indexed by entity and field, and by subject.
DwPolicyVersionA single row carrying the snapshot version.

One serializer, three stores

PolicyPayload is the only place a rule payload is read or written. Neither provider parses JSON of its own, because three parsers would become three standards. Every enumeration is written and read by name, in JSON and in a database column alike: the zero member of several enumerations is the permissive one, so an unparsed value must not read as a plausible-looking default.

Read-only deployments

Register only IDwPolicyStore and writes become impossible by construction rather than by convention. IDwPolicyWritableStore is a separate interface, so a replica that should never accept a rule simply does not implement it.

The conformance suite

One abstract suite runs against in-memory, a real Redis and a real PostgreSQL, covering load, version, watch and poll, the atomic swap, the zone split, startup failure, refresh failure, the staleness ceiling, the sealed-field refusal and the read-only store. Any future provider inherits all of it.

It fails rather than skips
The suite needs a Docker daemon and refuses to run without one. A conformance leg that quietly does not run reads as three stores agreeing when only one of them was checked.