Skip to content

Marten 9.23.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 11 Aug 19:57
· 281 commits to master since this release

Highlights

Store-agnostic document abstractions (#5216)

Marten now implements the JasperFx.Events.Documents contract added in JasperFx 2.47.0, the shared document session surface behind the Wolverine aggregate-handler unification (wolverine#3907).

Marten JasperFx contract
IQuerySession IDocumentReadOperations
IDocumentOperations IDocumentWriteOperations
IDocumentSession IDocumentSessionOperations
IDocumentStore IDocumentSessionFactory<IDocumentSession, IQuerySession>
MartenLinqQueryProvider IDocumentQueryExecutor

Marten already had every operation with matching signatures, so this is additive — existing code is unaffected. Marten passes all 42 tests in the shared document storage compliance suite.

Fixes

#5210 — UseListenNotifyForEventAppends corrupted inline projections in the same transaction. NotifyEventAppendedOperation was marked NoDataReturnedCall but emitted select pg_notify(...), which returns a one-row result set. OperationPage.ApplyCallbacksAsync never advances the reader past a NoDataReturnedCall, so every later operation in the batch read the wrong result set — surfacing as spurious ConcurrencyExceptions and wrong document versions, far from the cause. Now uses the DO $$ BEGIN PERFORM pg_notify(...); END $$ form, which fires the identical notification and returns nothing.

HardDeleteWhere<T> could not remove already soft-deleted rows. It inherited the soft-delete exclusion filter, so a retention or purge sweep returned cleanly while leaving behind exactly the rows it existed to remove. HardDelete by id was unaffected, which made the API look correct when spot-checked.

#5159 — UseOptimisticConcurrency and UseNumericRevisions were asymmetric. UseNumericRevisions(true) cleared the competing Guid version metadata; UseOptimisticConcurrency(true) did not clear the numeric revision metadata. The order of the two fluent calls therefore decided whether the configuration worked or threw at bootstrap. Both orders are now last-wins.

The bootstrap guard is narrowed rather than dropped, and now reports which case it is:

  • a projection-target document, where numeric revisions are load-bearing for the projection machinery, still throws
  • a revision declared on your own type (IRevisioned, ILongVersioned, a long [Version] member) still throws, naming the member — honoring the override would leave that property permanently unmapped
  • everything else is last-wins

#5131 — StreamPagedByCursor<T> published typeof(void) as its OpenAPI response type, so endpoints returning it advertised a 200 with no schema at all. Now publishes a new CursorPagedResult<T>, mirroring what StreamPaged<T> already does with PagedResult<T>.

Hardening and docs

#5213 — an audit test over the NoDataReturnedCall invariant. Reflects over every implementation and asserts its SQL cannot return a result set, so the class of bug behind #5210 goes red at build time rather than surfacing as a version mismatch three operations later.

#5212 — regression pins ported from Polecat, covering DeleteWhere not re-stamping mt_deleted_at on already-deleted rows.

#5217 — documented Npgsql's Max Auto Prepare connection-string knob, including measurements showing no resolvable effect on read latency, and the reasons Marten does not enable prepared statements: generic plans displacing parameter-aware ones (which matters most under conjoined multi-tenancy, where a skewed tenant_id sits in nearly every predicate), per-connection plan memory, and runtime DDL invalidating cached plans.

New public API

  • Marten.AspNetCore.CursorPagedResult<T>
  • IQuerySession, IDocumentOperations, IDocumentSession and IDocumentStore now implement the corresponding JasperFx.Events.Documents contracts

Both are additive.

Not shipped

The opt-in LINQ query plan cache proposed in #5013 / #5018 was closed rather than merged. Benchmarking showed the cost it removes — LINQ parsing plus SQL generation — is 1.2 μs on the fastest realistic Marten query, against 28–56 μs of run-to-run variance on that same query. On a warm cache hit it allocated more than no cache at all, and in a conjoined multi-tenant store it cached nothing while paying two full LINQ parses per query. Details on #5018.

Dependencies

  • JasperFx and JasperFx.Events 2.46.0 → 2.47.0
  • Weasel 9.24.0 (unchanged)

Full Changelog: V9.22.6...V9.23.0