Marten 9.44.0
Bumps the JasperFx family to 2.79.1 and lands three fixes. Two of them change behaviour, one in a way that changes what a query returns.
Fixes
#5549 — a nested Any() with a plain equality silently returned nothing. A sub-query is parsed against a member tree rooted at the element type, so inside Middles.Any(m => m.Bottoms.Any(...)) the Bottoms member reports d.data -> 'Bottoms' — within that parse, d.data stands for one Middle. Nothing in the member tree records that Bottoms lives under Middles; the containment filter is re-anchored when the enclosing Any() lifts it out. That re-anchoring moved the jsonb payload but left the locator pointing at the inner collection, so the query went out as
d.data -> 'Bottoms' @> '[{"Middles":[{"Name":"Bill"}]}]'Well-formed SQL against a key the document does not have. No error, no rows.
Three shapes of the same query diverged, which is what made this hard to pin down from the outside: a sibling predicate (m.Color == Blue && m.Bottoms.Any(...)) built the containment through a different path and was always correct, StartsWith is not containment-eligible and fell to a jsonpath filter that re-rooted properly, and only the plain single-equality nested Any() was wrong.
Four more failures came out of the same defect once the generated SQL was dumped across every nesting shape:
| Shape | Before |
|---|---|
the nested Any() negated, or as an OR branch, or three levels deep |
same wrong locator |
m.Bottoms.Any(x) && m.Bottoms.Any(y) — the workaround suggested in the issue |
the two merged into one payload element: one of the two values was silently dropped, and a single element had to satisfy both |
| three levels deep ending in a value collection | NotSupportedException |
a value-collection Any()/Contains() beside a sibling predicate |
NullReferenceException |
t.Xs.Any(p) && t.Xs.Any(q) now matches when two different elements satisfy the two predicates. That is what the query asks for, and it is more rows than 9.43 returned. If you were relying on the old answer, you wanted t.Xs.Any(x => p(x) && q(x)).
Diagnostics surface (monitoring consoles)
#932 — DocumentTypesAsync lists document sub-classes. The contract was silent on this and the stores split: Polecat listed sub-classes, Marten and Fisher did not, and a store-agnostic type picker could not tell which answer it had. Settled in favour of listing them, because every member taking a documentTypeName already accepts a sub-class name and narrows to its rows — a picker that could not offer one was hiding a capability the contract guarantees.
Marten now emits an entry per registered sub-class alongside each mapped root. A sub-class entry carries its own mt_doc_type alias — the discriminator its rows actually hold, which is the string you pass back to QueryDocumentsAsync — and the root's schema, because the root's table is where those rows live. DocumentTypeRef.RootTypeName names the root and is null for a root itself; IsSubClass reads it.
DocumentTypesAsync than it did in 9.43. IsSubClass tells them apart. RootTypeName is init-only, so the positional DocumentTypeRef shape a store compiled against an older JasperFx calls is unchanged.
Event sourcing
A composite projection could intermittently pause and stop advancing (#938). ProjectionExecution disposed its batch unconditionally through an await using. For a composite member that batch is the parent composite's, shared by design — so the member's scope-exit disposal raced the parent's execute, which then threw ObjectDisposedException out of Block.WaitForCompletionAsync and left the shard Paused with no progression row at all.
It takes a member on that path, so the exposed shape is a composite of an aggregation plus a plain event projection; an all-aggregation composite runs on GroupedProjectionExecution, which already guarded the disposal. Intermittent because the stages run in parallel and the race can fall either way — measured at roughly one run in eight on one such composite.
If you run composite projections with a non-aggregation member and have seen a shard quietly stop advancing, this is a strong candidate. Fixed in JasperFx 2.79.1.
Dependencies
JasperFx 2.79.1. Alongside #932 and #938 above: #930 adds LoadManyAsync to IDocumentReadOperations and FetchManyForWriting to IEventStoreOperations, #931 default-implements the 2.77 IDocumentStoreDiagnostics members so a store built against an older JasperFx still loads, and #933 ratchets abstract interface members so contract growth stays deliberate.
The new members arrive default-implemented and Marten has not yet overridden FetchManyForWriting / LoadManyAsync — the defaults are correct but call the single-id member in a loop. Batching them against Marten's existing batch-query API is tracked separately.