jsonb event dataIf you store the EF Core event-data column as jsonb (recommended on Postgres), the default event-property filter — a substring match on the text representation — silently stops matching. Postgres normalises jsonb to a canonical form with a space after every colon, so LIKE '%"Name":"Alice"%' never finds the row that contains {"Name": "Alice"}.
The Memoria.EventSourcing.Store.EntityFrameworkCore.Npgsql package replaces the default filter with one that uses the Postgres @> JSON-containment operator. It works regardless of whitespace formatting and is GIN-indexable.
For the registration call, see Configuration: Entity Framework Core.
| Stored as | Serialised form returned by Postgres |
|---|---|
text |
{"Name":"Alice"} (as written) |
jsonb |
{"Name": "Alice"} (canonical) |
Without the Npgsql filter, eventPropertyFilter = { "Name": "Alice" } queries silently miss every row stored as jsonb. There is no error — just an empty result set. This is the failure mode this package exists to fix.
jsonbThe new filter requires the events.Data column to be a JSON column. Configure this in your DbContext’s OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<EventEntity>()
.Property(e => e.Data)
.HasColumnType("jsonb");
}
Add a GIN index when streams grow large and property filtering becomes a hot path:
modelBuilder.Entity<EventEntity>()
.HasIndex(e => e.Data)
.HasMethod("gin");
For eventPropertyFilter = { "OrderId": "abc-123" }, the filter generates SQL roughly equivalent to:
WHERE "Data" @> '{"OrderId":"abc-123"}'::jsonb
Property names and values are JSON-encoded before being passed to Postgres, so quotes and backslashes are safe.
IEventDataFilter is a public abstraction in Memoria.EventSourcing.Store.EntityFrameworkCore.Filtering. Provide your own implementation and register it before calling AddMemoriaEntityFrameworkCore<TDbContext>():
services.AddSingleton<IEventDataFilter, MyFilter>();
services.AddMemoriaEntityFrameworkCore<ApplicationDbContext>();
The default registration uses TryAdd, so a pre-registered filter is honoured.