Summary
Marten implements the tenant-scoped IEventStore.GetProjectionStatusesAsync(string? tenantId, ct) explorer overload (#4596 / #502), but two sibling tenant-scoped read overloads from #503 are not overridden and fall through to the JasperFx default that throws for a non-null tenant:
IEventStore.ReadStreamAsync(string streamId, string? tenantId, CancellationToken ct)
IEventStore.GetRecentStreamsAsync(int count, string? tenantId, CancellationToken ct)
(Confirmed by reflection against the released Marten 9.17.1 / JasperFx.Events 2.32.0: GetInterfaceMap shows both resolving to the interface default, i.e. "throws non-null tenant".) GetStreamMetadataAsync(streamId, tenantId, ct) should be checked/implemented for parity as well.
Why
On a conjoined multi-tenant store the same stream id can exist under two tenants; the tenant-less ReadStreamAsync reads across every tenant and returns their ambiguous union. CritterWatch's Event Store Explorer (CritterWatch #782) threads a tenantId through and calls these overloads to isolate a single tenant's slice — but today a picked tenant just throws.
Approach (proven locally, all 16 explorer tests + 2 new isolation tests green)
Both IEventStore read impls in src/Marten/DocumentStore.EventStoreExplorer.cs already select … tenant_id over an AllowAnyTenant explorer session, so the tenant-scoped overloads mirror the two-model shape the existing GetProjectionStatusesAsync(tenantId) uses (#502):
- Single database (conjoined tenancy): scope with a
where tenant_id = @tenant_id predicate on the same AllowAnyTenant session.
- Database-per-tenant / sharded (
Options.Tenancy.Cardinality != DatabaseCardinality.Single): open the session against Tenancy.FindOrCreateDatabase(tenantId); no column filter needed.
null tenant → delegate to the existing tenant-less overload (byte-identical to today).
The tenant-less overloads become thin delegators to the new tenant overloads with tenantId: null.
I have a ready patch (diff against tag V9.17.0, where the explorer file matches the release; note the current fix/4918 branch is on an older JasperFx pin that predates #503's interface overloads) — happy to open a PR.
Related
Summary
Marten implements the tenant-scoped
IEventStore.GetProjectionStatusesAsync(string? tenantId, ct)explorer overload (#4596 / #502), but two sibling tenant-scoped read overloads from #503 are not overridden and fall through to the JasperFx default that throws for a non-null tenant:IEventStore.ReadStreamAsync(string streamId, string? tenantId, CancellationToken ct)IEventStore.GetRecentStreamsAsync(int count, string? tenantId, CancellationToken ct)(Confirmed by reflection against the released Marten 9.17.1 / JasperFx.Events 2.32.0:
GetInterfaceMapshows both resolving to the interface default, i.e. "throws non-null tenant".)GetStreamMetadataAsync(streamId, tenantId, ct)should be checked/implemented for parity as well.Why
On a conjoined multi-tenant store the same stream id can exist under two tenants; the tenant-less
ReadStreamAsyncreads across every tenant and returns their ambiguous union. CritterWatch's Event Store Explorer (CritterWatch #782) threads atenantIdthrough and calls these overloads to isolate a single tenant's slice — but today a picked tenant just throws.Approach (proven locally, all 16 explorer tests + 2 new isolation tests green)
Both
IEventStoreread impls insrc/Marten/DocumentStore.EventStoreExplorer.csalreadyselect … tenant_idover anAllowAnyTenantexplorer session, so the tenant-scoped overloads mirror the two-model shape the existingGetProjectionStatusesAsync(tenantId)uses (#502):where tenant_id = @tenant_idpredicate on the sameAllowAnyTenantsession.Options.Tenancy.Cardinality != DatabaseCardinality.Single): open the session againstTenancy.FindOrCreateDatabase(tenantId); no column filter needed.nulltenant → delegate to the existing tenant-less overload (byte-identical to today).The tenant-less overloads become thin delegators to the new tenant overloads with
tenantId: null.I have a ready patch (diff against tag
V9.17.0, where the explorer file matches the release; note the currentfix/4918branch is on an older JasperFx pin that predates #503's interface overloads) — happy to open a PR.Related
QueryByTagsAsync+EventQuery.TenantId, needed for CritterWatch Docs for event store stream identity. #798 §4) and JasperFx/polecat (same read surface, implements none today).