JasperFxEventProjectionBase's constructor never touches Options, so an EventProjection registers no teardown targets: both Options.CleanUps and Options.StorageTypes stay empty for the projection's whole lifetime unless the author hand-calls Options.DeleteViewTypeOnTeardown<T>().
Compare JasperFxAggregationProjectionBase.cs:58, where the base constructor does it for you:
Options.DeleteViewTypeOnTeardown<TDoc>(); // -> CleanUps + StorageTypes
RegisterPublishedType(typeof(TDoc));
JasperFxEventProjectionBase.cs:36-39 calls only RegisterPublishedType. So the two projection families behave differently on teardown, with nothing in the API surface signalling it.
Consequences
Anything that derives "what does this projection own" from CleanUps or StorageTypes silently does nothing for event projections:
- Rebuild teardown.
Options.Teardown(session) iterates CleanUps, so a rebuild of an EventProjection deletes the progression row and then re-projects into a table that still holds the previous run's documents.
- Per-tenant progression deletion. Same path, per tenant.
- The
ProjectionScenario test harness wipe — All.SelectMany(x => x.Options.StorageTypes), identical in Marten (Events/TestSupport/ProjectionScenario.cs:31-35) and Polecat (Events/TestSupport/ProjectionScenario.cs:35-43). The harness reached by Advanced.EventProjectionScenario does not wipe after event projections.
This is already known and worked around in at least one place rather than fixed. From a Marten test, TenantPartitionedEventsTests/Projections/event_projection_per_tenant.cs:172-178:
EventProjection (vs Single/MultiStreamProjection) does NOT auto-register its output document types as teardown targets — … CleanUps stays empty unless the projection declares it. Without this, DeleteProjectionProgressAsync(name, tenantId, …) deletes the progression row only — projected docs survive and a subsequent rebuild double-writes.
followed by an explicit Options.DeleteViewTypeOnTeardown<LegLog>();. Every author of an event projection has to know that independently.
Why it's fixable
An event projection can publish several document types, which is presumably why the base constructor can't do what the aggregation base does with its single TDoc. But the types are known: the source generator emits RegisterPublishedType(typeof(...)) per published type (JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs:619,672), so PublishedTypes() is populated.
So teardown targets could default to the published types — registered after construction, once RegisterPublishedType calls have run — with an opt-out for projections that write into storage they don't want truncated.
Points worth deciding:
- Published types include everything the projection creates. Truncating all of them on rebuild is almost certainly right, but it is a behavior change for anyone currently relying on the silence.
DeleteViewTypeOnTeardown writes to both CleanUps and StorageTypes (AsyncOptions.cs:98-112); StorageTypes is documented as a schema build-out hint but in practice is a by-product of teardown registration. If event projections start populating it, the harness wipe above starts working as a side effect — desirable here, but it's the kind of coupling worth being deliberate about.
Found while analysing marten#5169, which is the same class of failure for composite projections. Affects Marten and Polecat identically.
JasperFxEventProjectionBase's constructor never touchesOptions, so anEventProjectionregisters no teardown targets: bothOptions.CleanUpsandOptions.StorageTypesstay empty for the projection's whole lifetime unless the author hand-callsOptions.DeleteViewTypeOnTeardown<T>().Compare
JasperFxAggregationProjectionBase.cs:58, where the base constructor does it for you:JasperFxEventProjectionBase.cs:36-39calls onlyRegisterPublishedType. So the two projection families behave differently on teardown, with nothing in the API surface signalling it.Consequences
Anything that derives "what does this projection own" from
CleanUpsorStorageTypessilently does nothing for event projections:Options.Teardown(session)iteratesCleanUps, so a rebuild of anEventProjectiondeletes the progression row and then re-projects into a table that still holds the previous run's documents.ProjectionScenariotest harness wipe —All.SelectMany(x => x.Options.StorageTypes), identical in Marten (Events/TestSupport/ProjectionScenario.cs:31-35) and Polecat (Events/TestSupport/ProjectionScenario.cs:35-43). The harness reached byAdvanced.EventProjectionScenariodoes not wipe after event projections.This is already known and worked around in at least one place rather than fixed. From a Marten test,
TenantPartitionedEventsTests/Projections/event_projection_per_tenant.cs:172-178:followed by an explicit
Options.DeleteViewTypeOnTeardown<LegLog>();. Every author of an event projection has to know that independently.Why it's fixable
An event projection can publish several document types, which is presumably why the base constructor can't do what the aggregation base does with its single
TDoc. But the types are known: the source generator emitsRegisterPublishedType(typeof(...))per published type (JasperFx.Events.SourceGenerator/EvolverCodeEmitter.cs:619,672), soPublishedTypes()is populated.So teardown targets could default to the published types — registered after construction, once
RegisterPublishedTypecalls have run — with an opt-out for projections that write into storage they don't want truncated.Points worth deciding:
DeleteViewTypeOnTeardownwrites to bothCleanUpsandStorageTypes(AsyncOptions.cs:98-112);StorageTypesis documented as a schema build-out hint but in practice is a by-product of teardown registration. If event projections start populating it, the harness wipe above starts working as a side effect — desirable here, but it's the kind of coupling worth being deliberate about.Found while analysing marten#5169, which is the same class of failure for composite projections. Affects Marten and Polecat identically.