#4668 — tenant-aware RebuildSingleStreamAsync overloads on AdvancedOperations#4674
Merged
jeremydmiller merged 1 commit intoJun 5, 2026
Merged
Conversation
…erations Adds two new overloads on AdvancedOperations: Task RebuildSingleStreamAsync<T>(string streamKey, string tenantId, CancellationToken token = default) Task RebuildSingleStreamAsync<T>(Guid streamId, string tenantId, CancellationToken token = default) Per the issue: under conjoined multi-tenancy and under per-database multi-tenancy users can open tenant-scoped sessions and append/aggregate events by tenant, but the store-level rebuild API had no way to express "rebuild this stream for tenant X" — forcing users to drop out of RebuildSingleStreamAsync and aggregate-then-persist manually. Implementation: * string overload: LightweightSession(tenantId) + AggregateStreamAsync(key) + Store + Save. Tenant id flows through both the read and the upsert. * Guid overload: mirrors the existing Guid overload's SessionOptions posture (ConcurrencyChecks = Disabled) + carries TenantId on the options object. Same read/upsert tenant routing as the string overload. Backwards-compat shape: NEW METHODS, NOT default-argument additions on the existing signatures. Per the issue's explicit request — keeping the (string id, CT) and (Guid id, CT) overloads bind-compatible at the call site for every existing caller. No optional argument anywhere on this path. Tests (src/MultiTenancyTests/rebuild_single_stream_tenant_overloads.cs): Four tests covering the matrix of tenancy style × stream-identity: * conjoined_rebuild_by_guid_with_tenant_id * conjoined_rebuild_by_string_key_with_tenant_id * per_database_rebuild_by_guid_with_tenant_id (MultiTenantedWithSingleServer) * per_database_rebuild_by_string_key_with_tenant_id (MultiTenantedWithSingleServer) Each test: * Seeds two tenants with different event shapes (3x AEvent vs 2x BEvent). * Calls the new overload to rebuild tenant A's stream. * Asserts tenant A's projection reflects ONLY A's events. * Repeats for tenant B; asserts B's projection reflects only B's events. * Conjoined-Guid test additionally pins cross-tenant isolation: loading tenant B's stream id through a tenant A session returns null (the rebuild stayed scoped to the supplied tenant). The per-database variants exercise MultiTenantedWithSingleServer routing end-to-end (a host + AddMarten + ApplyAllDatabaseChangesOnStartup bootstrap), so the rebuild's session has to route to each tenant's physical database for both the event load and the upsert. Verified locally on net9.0: * Build clean (Marten + MultiTenancyTests). * All 4 new tests pass; total 1.7s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Jun 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4668.
Adds two new overloads on
AdvancedOperations:Backwards-compatibility shape
New methods, not default-argument additions on the existing signatures.
Per the issue's explicit request — keeping
(string id, CT)and(Guid id, CT)bind-compatible at the call site for every existing caller. No optional
argument anywhere on this path.
Implementation
(string streamKey, string tenantId, CT)LightweightSession(tenantId)(string, CT)overload's session shape, scoped to the tenant.(Guid streamId, string tenantId, CT)LightweightSession(new SessionOptions { ConcurrencyChecks = Disabled, TenantId = tenantId })ConcurrencyChecks.Disabledposture, withTenantIdset on the options.Tenant id flows through both the
AggregateStreamAsyncread and theprojected-document upsert.
Tests
src/MultiTenancyTests/rebuild_single_stream_tenant_overloads.cs— fourtests covering the matrix of tenancy style × stream-identity:
conjoined_rebuild_by_guid_with_tenant_idconjoined_rebuild_by_string_key_with_tenant_idper_database_rebuild_by_guid_with_tenant_idMultiTenantedWithSingleServerper_database_rebuild_by_string_key_with_tenant_idMultiTenantedWithSingleServerEach test:
AEventvs 2×BEvent).stream id through a tenant A query session returns null.
The per-database variants exercise
MultiTenantedWithSingleServerrouting end-to-end (a host +
AddMarten+ApplyAllDatabaseChangesOnStartupbootstrap), so the rebuild's session has to route to each tenant's
physical database for both the event load and the upsert.
Verification
Local on net9.0:
🤖 Generated with Claude Code