EventProjectionScenario's wipe covers composite projections (#5169) - #5185
Merged
Conversation
`ProjectionScenario.DeleteExistingDataAsync` derived its wipe list from `Options.StorageTypes`. A `CompositeProjection` never populates its own `StorageTypes` — its members hold theirs — so for a store whose entire read side is one composite the loop iterated nothing: event data was deleted, the composite's read models were not, and every scenario after the first ran against the previous scenario's documents, now orphaned from any events. Because the events *were* deleted, nothing could reconstruct or notice them. Silently, and it undercuts exactly the guarantee the harness leads with. Tests stayed accidentally correct as long as they used unique ids per scenario; any unfiltered `Query<T>()` inside `AssertAgainstProjectedData` saw prior scenarios' rows. The wipe now walks `PublishedTypes()`. That is the traversal that already knows to expand a composite into its members (`CompositeProjection<,>.PublishedTypes()` unions its stages), it is a superset of `StorageTypes` for every `ProjectionBase`, and it is what Marten's own schema build-out uses — unlike `StorageTypes`, which is documented as a schema-building hint rather than a teardown list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017CTtw2kVRSZKp1p5RTxgAy
This was referenced Aug 5, 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 #5169.
The bug
ProjectionScenario.DeleteExistingDataAsyncbuilt its wipe list fromOptions.StorageTypes:A
CompositeProjectionnever populates its ownOptions.StorageTypes(norCleanUps) — its members hold theirs. So for a store whose read side is one composite over ~10 members, the loop iterated nothing. Event data was deleted, the composite's read models were not, and every scenario after the first ran against the previous scenario's documents while their events were gone. Because the events were deleted, nothing downstream could reconstruct or even notice the leftovers.Tests stayed accidentally correct as long as they used unique ids per scenario. Any unfiltered
Query<T>()insideAssertAgainstProjectedDatasaw prior scenarios' rows — which is how the reporter hit it: an orderedQuery<InvoiceOverviewItem>()asserting[100m, 200m]returned[90m, 100m, 100m, 200m, 250m].The fix
The wipe walks
PublishedTypes()instead:That is the right traversal for three independent reasons:
CompositeProjection<,>.PublishedTypes()already unions its stages' members, so composites expand correctly with no new machinery;Options.StorageTypesfor everyProjectionBase(PublishedTypes()=_publishedTypes ∪ Options.StorageTypes), so nothing that was wiped before stops being wiped;StorageFeatures→AllPublishedTypes()), whereasStorageTypesis documented as a schema-building hint — "used to help build out schema objects if the async daemon is started before the rest of the application" — not a teardown list.That covers the issue's suggested fix 1. Fix 2 (having the composite populate its own
StorageTypes/CleanUpsfrom its members) is deliberately not taken: duplicating the members'CleanUpsonto the parent would make the rebuild teardown in #5175 truncate every member table twice, and the schema-build concern it was meant to address is already handled byPublishedTypes().Tests
DaemonTests/EventProjections/Bug_5169_composite_scenario_wipe.cs:the_composite_reports_the_document_types_its_members_write— the composite'sPublishedTypes()carries both a stage-1 snapshot type and a stage-2 read model, and itsOptions.StorageTypesis empty (documenting why the old source could not work)a_second_scenario_starts_from_a_clean_slate— the issue's repro: two sequentialEventProjectionScenarioruns over a composite; the second must see exactly one invoice and one overview, and the first scenario's documents must be goneFalsified: reverting the one-line change makes
a_second_scenario_starts_from_a_clean_slatefail. All 26EventProjections+Bug_5169tests pass on net10.0.Adjacent to #5175, which is the same family (a composite's members being invisible from the outside) but a different mechanism — rebuild teardown reading each member's
CleanUps.🤖 Generated with Claude Code
https://claude.ai/code/session_017CTtw2kVRSZKp1p5RTxgAy