Repository navigation
Marten 9.40.0
Minor rather than patch for one reason: the source-generated DetermineAction now decides one action per batch rather than per event. See the behaviour change below before upgrading if you archive streams from a projection.
⚠️ Behaviour changes
Archiving projections: one action per batch, not per event (JasperFx/jasperfx#889, via the JasperFx 2.75.2 bump). The source-generated DetermineAction used to decide per event; it now decides once per batch. This changes what an archiving projection does when a single batch mixes an archive with later events for the same stream. StreamArchivingCompliance gained facts for it and Marten passes them, but if you depend on the old per-event evaluation, test before upgrading.
TenantIdStyle is now applied where the tenant id is stamped and keyed, not only where a tenant is resolved (#5516). Under ForceLowerCase, these three previously wrote or matched the raw id:
BulkInsertEventsAsync/BulkInsertEventStreamAsyncresolved the right database or partition and then stamped the raw id on everymt_streams/mt_eventsrow — rows invisible to any normalised read under conjoined tenancy, and the wrong key inside the right partition underUseTenantPartitionedEvents.TenantIsOneOf(...)filtered on the raw values, so a mixed-case id matched nothing.DeleteAllTenantDataAsyncused the raw id as itsDELETE ... WHERE tenant_id = :pparameter, so it matched no rows and reported success — the one to look at hardest, since callers are usually honouring an erasure request.
If you have been calling these with mixed-case tenant ids, data already written under the raw casing stays where it is; it will not be found by a normalised read. ForTenant(string) also now normalises its nested-session cache key, which makes ForTenant("RED") and ForTenant("red") one session rather than two.
Event store
IEventStore.OpenReadOnlyEventStore(tenantId)is implemented (#5513). WithAdvanced.DefaultTenantUsageEnabled = false— the automatic state once database-per-tenant tenancy is configured — the entireIReadOnlyEventStoresurface was unreachable, because the tenant-less overload opens the store's default session and is refused before any tenant scope applies. That is whyQueryStreamStates(tenantId)'s own parameter could not work around it. A null tenant keeps the previous store-global behaviour.- Diagnostic event-store reads answer "no results" instead of throwing (#5511, #5512), decided from the database rather than from configuration.
- Progression and dead-letter reads answer nothing for an event store nothing provisions (#5509, #5510).
Diagnosing a missing source-generated dispatcher
The JasperFx 2.75.2 bump (#5518) carries three changes that came out of #5495, where a user's aggregates compiled with no dispatcher and the runtime message could not tell them why:
- The generator now leaves
[assembly: JasperFxSourceGeneratorApplied]in every assembly it processes, so "the generator never ran in that assembly" — a csproj problem — is distinguishable at runtime from "the generator ran and declined your type" — a code-shape problem. The exception says which. - Two copies of the generator in one compilation no longer collide with
CS0433. This happens when a project references the standaloneJasperFx.Events.SourceGeneratorpackage as well as Marten, which bundles it: both copies emit identical file paths and the file-scoped evolvers mangle to the same name. If you hit that error — recognisable because it names the same assembly on both sides — remove the standalone package reference; Marten already carries the analyzer. - Opt in to a build-time check with
<JasperFxEventsRequireSourceGenerator>true</JasperFxEventsRequireSourceGenerator>on projects that declare aggregates, and a missing analyzer becomes build errorJFXEVT900rather than a failure at the firstFetchForWriting.
Compliance coverage
Marten is now enrolled in DocumentConjoinedTenancyCompliance (#5517) — the first shared document coverage of conjoined tenancy on any store — with all 10 facts running. ConjoinedEventTenancyCompliance goes from 16 passing with 2 skipped to 18 passing with none, the two skips having been waiting on the read-only overload above.
Two of these enrollments exposed that Marten's own commit-listener compliance facts had been passing vacuously — the harness never installed the listener the suites register, so "no phantom deletion was reported" was trivially true of a store reporting nothing at all. Fixed in the #5518 bump; no product change was needed, and both halves of that fact now pass for real.
Dependencies
JasperFx 2.75.2 and Weasel 9.35.2.
Weasel 9.35.x is mostly EF Core and SQL Server work, plus weasel#634: an exhausted DropSchema retry is now reported instead of returning success.
Docs
The bundled analyzer does flow transitively across a ProjectReference (#5508) — the composite-configuration page claimed the opposite. The claim was true of the standalone analyzer package, which Marten sets PrivateAssets="all" on, but #4557 bundles the analyzer DLL into Marten's own package and that copy flows.