Skip to content

Expose SliceGroup.TryFindUpstreamCache for downstream projection enrichment#205

Merged
jeremydmiller merged 1 commit intomainfrom
expose-upstream-cache
May 6, 2026
Merged

Expose SliceGroup.TryFindUpstreamCache for downstream projection enrichment#205
jeremydmiller merged 1 commit intomainfrom
expose-upstream-cache

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Companion change for JasperFx/marten#4329.

A composite projection runs all of its stages against a single in-memory `IProjectionBatch` that flushes to the database once, after every stage completes. Document writes produced by stage 1 are still queued in memory while stage 2 enriches, so a SQL query in a downstream stage cannot see them — a trap that's easy to fall into when writing custom `EnrichUsingEntityQuery` callbacks.

`SliceGroup<TDoc, TId>` already has a private `findCache<TEntityId, TEntity>()` helper that walks `Upstream` and pulls the upstream stage's in-memory aggregate cache for entities of type `TEntity`. This is the mechanism the `EnrichWith().AddReferences()` chain uses to read in-flight upstream output without going to SQL.

This PR exposes that lookup as a public API so custom enrichment callbacks (especially inside `EnrichUsingEntityQuery`) can do the same lookup for arbitrary upstream entity types — not only the `TEntity` of the enclosing `EnrichWith`.

```csharp
public bool TryFindUpstreamCache<TEntityId, TEntity>(
[NotNullWhen(true)] out IAggregateCache<TEntityId, TEntity>? cache);
```

Returns `false` when no upstream stage of the composite is producing entities of that type. Otherwise, returns a per-tenant cache backed by the upstream stage's in-memory aggregate cache.

Test plan

  • 3 new unit tests in `SliceGroupTests`: empty `Upstream`, single matching upstream, walks past non-matching upstreams.
  • All 71 `EventTests/Projections` tests pass on net8.0 / net9.0 / net10.0.
  • No change to existing internal `findCache` callers.

🤖 Generated with Claude Code

…types

Composite-projection downstream stages can already consult the
upstream stages' in-memory aggregate cache when they go through the
EnrichWith<T>().AddReferences() chain (which internally calls the
private findCache<TEntityId, TEntity>). Custom enrichment callbacks
inside EnrichUsingEntityQuery had no way to do the same lookup for a
type other than the EnrichWith<T> type — which is the exact scenario
that lured users into SQL-querying upstream stage output and getting
empty results because the writes are still queued in the in-memory
ProjectionUpdateBatch and not yet committed (see JasperFx/marten#4329).

Add a public TryFindUpstreamCache<TEntityId, TEntity> on SliceGroup
that returns the per-tenant upstream cache, or false when no upstream
stage of the composite is producing entities of that type. This is a
strict superset of the existing internal lookup; nothing else changes.

Refs JasperFx/marten#4329.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 0ebed94 into main May 6, 2026
1 check passed
@jeremydmiller jeremydmiller deleted the expose-upstream-cache branch May 6, 2026 17:28
jeremydmiller added a commit that referenced this pull request May 6, 2026
Public API addition: SliceGroup<TDoc, TId>.TryFindUpstreamCache<TEntityId, TEntity>
lets composite-projection downstream stages look up the in-memory aggregate
cache of an arbitrary upstream entity type from inside a custom enrichment
callback (notably EnrichUsingEntityQuery).

Refs JasperFx/marten#4329, #205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit to JasperFx/marten that referenced this pull request May 6, 2026
…egration test, docs

JasperFx.Events 1.34.0 ships a public SliceGroup<TDoc, TId>.TryFindUpstreamCache
that lets a custom enrichment callback look up an upstream stage's in-memory
aggregate cache for arbitrary entity types — the previously-only-internal hook
that EnrichWith<T>().AddReferences() relies on.

This commit:

- Bumps JasperFx 1.29.0 → 1.29.1 and JasperFx.Events 1.33.1 → 1.34.0 in
  Directory.Packages.props.
- Adds Bug_4329_try_find_upstream_cache integration test that runs the exact
  shape from #4329 (single composite batch where stage 1 produces an Order and
  stage 2 needs to read it). Without this API the only way for stage 2 to read
  upstream Order data inside a custom EnrichEventsAsync would be to query SQL
  (which returns empty) or to listen for Updated<Order>; with the new API the
  downstream stage can pull the in-flight Order from the upstream cache by id.
- Wires the new option into the "Cross-stage document visibility" section of
  composite.md and the EnrichUsingEntityQuery warning in enrichment.md, marked
  with a JasperFx.Events 1.34 badge. The composite.md sample is sourced from
  #region sample_try_find_upstream_cache in the new test file.

Refs #4329, JasperFx/jasperfx#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit to JasperFx/marten that referenced this pull request May 7, 2026
…egration test, docs

JasperFx.Events 1.34.0 ships a public SliceGroup<TDoc, TId>.TryFindUpstreamCache
that lets a custom enrichment callback look up an upstream stage's in-memory
aggregate cache for arbitrary entity types — the previously-only-internal hook
that EnrichWith<T>().AddReferences() relies on.

This commit:

- Bumps JasperFx 1.29.0 → 1.29.1 and JasperFx.Events 1.33.1 → 1.34.0 in
  Directory.Packages.props.
- Adds Bug_4329_try_find_upstream_cache integration test that runs the exact
  shape from #4329 (single composite batch where stage 1 produces an Order and
  stage 2 needs to read it). Without this API the only way for stage 2 to read
  upstream Order data inside a custom EnrichEventsAsync would be to query SQL
  (which returns empty) or to listen for Updated<Order>; with the new API the
  downstream stage can pull the in-flight Order from the upstream cache by id.
- Wires the new option into the "Cross-stage document visibility" section of
  composite.md and the EnrichUsingEntityQuery warning in enrichment.md, marked
  with a JasperFx.Events 1.34 badge. The composite.md sample is sourced from
  #region sample_try_find_upstream_cache in the new test file.

Refs #4329, JasperFx/jasperfx#205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant