Dependencies move to Weasel 9.27.0 and JasperFx 2.56.0.
Several fixes in this release share a failure mode worth calling out on its own: the write succeeded, the read disagreed, and nothing anywhere reported an error. Two of them ran undetected in production for weeks.
Patching
- #5290 — the patching API built its JSON paths from raw C# member names, so a member carrying
[JsonPropertyName](or[JsonProperty]under Newtonsoft) was patched at a path the serializer never reads. The patch reported success,LoadandQuerykept returning the old value, and the phantom node was erased by the next full save — so there was no durable evidence anything had gone wrong. Patch paths now resolve through the same member machinery the LINQ provider uses, soWhere(x => x.Name == v)andPatch(...).Set(x => x.Name, v)agree about where a member lives by construction. The predicate overloads had the same gap and are fixed with it. - #5295 — a patch that moves a value the schema also keeps in a
.Duplicate()column now refreshes that column in more of the cases that need it: an aliased member, a patch on a parent of the duplicated member, and the destinations of the patching API's ownDuplicateandRenameoperations. Left unfixed, the document and the index that exists to search it disagreed —Loadreturned the new value whileQueryfiltered on the same member returned nothing.
DCB
- #5268 —
Events.BuildHStoreTagIndexConcurrentlybuilds the hstore tag index without holdingACCESS EXCLUSIVEonmt_eventsfor the duration. On an ordinary event table that isCREATE INDEX CONCURRENTLY; underEvents.UseTenantPartitionedEvents, where PostgreSQL refusesCONCURRENTLYon a partitioned parent outright, Marten emits the per-partition sequence it does accept. Opt-in, because a concurrent build cannot run inside a transaction and so changes whatdb-patchanddb-dumpwrite out. The out-of-band route from 9.29.0 (Events.IgnoreIndex(EventGraph.HStoreTagIndexName)) still works and is still supported. - #5276 (thanks @Noblix) — a session that fetched a DCB boundary, decided it had nothing to append, and saved anyway still bumped the
mt_dcb_tag_versionrows, invalidating a concurrent session that did have something to append. A boundary is now only enforced when the save actually appends. #5280 covers the repeated-fetch half: each row is asserted exactly once, oldest capture wins. - #5283 / #5286 — tag rules.
Events.TagWith<T>(...)states once how an event is tagged and applies it wherever the event is built — ordinary appends,StartStream, aggregate handlers and bulk inserts alike — closing the gaps tag inference leaves open.Events.TagEventsBy(...)hands over a translator an application already owns instead of restating it one registration at a time.
LINQ
- #5279 (thanks @svenclaesson) — two enum value-collection filters that silently returned zero rows.
x.EnumArray.Contains(variable)resolved to theMemoryExtensions.Containscomparer overload rather thanEnumerable.Contains, because enums do not implementIEquatable<T>; withImplicitUsingson, most applications hit this by default. - #5287 — a
HashSet<TEnum>in aContainsfilter now projects to something Npgsql can bind. - #5289 (thanks @arnelirobles) — the parameterised
MatchesJsonPath(sql, params object[])overload threw for every call. It now works for any argument type, maps null ontoDBNull, and reports a placeholder/parameter count mismatch with a message naming both counts instead of anIndexOutOfRangeExceptionfrom inside the LINQ provider. Worth knowing:^is the placeholder character for that overload and is also a regex anchor, so it lands inside JSONPath literals by accident.
Async daemon
- #5277 — the skip-ahead probe asked for
min(seq_id)over a join, and PostgreSQL only rewritesMINinto an ordered index scan when the aggregate's input is a single relation. The probe therefore scanned every remaining row in the partition, on the one code path that exists precisely because the store is too large for the normal query to finish. - JasperFx 2.56.0 brings three high-water fixes reachable under load, including one from a deployment running 2,173 tenants across 512 shard databases (thanks @erdtsieck): a tenant agent seeded below its own committed position no longer pauses permanently, high water is primed exactly once so concurrent agent starts cannot read it at zero, and per-tenant catch-up no longer silently skips a tenant a concurrent reconciliation dropped. The middle one is not tenancy-specific — it is the shape behind an unexplained
Pausedshard after a rollout.