Skip to content

Add UsingStore<T> declarative enrichment from ancillary stores (#4300)#4301

Closed
erdtsieck wants to merge 2 commits intoJasperFx:masterfrom
erdtsieck:feature/ancillary-store-enrichment-4300
Closed

Add UsingStore<T> declarative enrichment from ancillary stores (#4300)#4301
erdtsieck wants to merge 2 commits intoJasperFx:masterfrom
erdtsieck:feature/ancillary-store-enrichment-4300

Conversation

@erdtsieck
Copy link
Copy Markdown
Contributor

@erdtsieck erdtsieck commented Apr 28, 2026

Summary

Implements the Marten side of declarative ancillary store enrichment (closes #4300).

  • Adds AncillaryStoreEnrichmentExtensions.UsingStore<TStore>() — an extension on SliceGroup.EntityStep<TEntity> that redirects enrichment lookups to a Lazy<TStore> ancillary store
  • Opens a lightweight session per enrichment call; session lifecycle (dispose) is handled automatically via WithAlternateSession from JasperFx.Events
  • Caching is preserved: the built-in upstream aggregate cache is checked before hitting the ancillary store
  • Tenant-aware: FetchProjectionStorageAsync handles tenant routing internally

Usage

public override async Task EnrichEventsAsync(
    SliceGroup<Order, Guid> group,
    IQuerySession querySession,
    CancellationToken cancellation)
{
    await group.EnrichWith<Product>()
        .UsingStore(_productStore)          // Lazy<IProductStore>
        .ForEvent<OrderPlaced>()
        .ForEntityId(e => e.ProductId)
        .EnrichAsync((slice, e, product) => e.Data.Product = product);
}

Dependencies

Requires JasperFx.Events WithAlternateSession support: JasperFx/jasperfx#195

Closes #4300

…Fx#4300)

Depends on JasperFx.Events WithAlternateSession support (jasperfx/jasperfx#<PR>).

- AncillaryStoreEnrichmentExtensions.UsingStore<TStore>() extends the
  EnrichWith<T>() chain to redirect entity lookups to a Lazy<TStore>
  ancillary store; the session is opened lazily and disposed automatically
  after enrichment completes
- Tenant-aware: FetchProjectionStorageAsync handles tenant routing internally
- ancillary_store_enrichment_tests: end-to-end integration test verifying
  that a product from an ancillary store is resolved and mapped into an
  Order projection via the async daemon

Closes JasperFx#4300
…nt (fixes JasperFx#4300)

- Store IServiceProvider on StoreOptions (set in AddMarten and AddMartenStore DI
  builders) and expose it via IStorageOperations.Services on DocumentSessionBase
- Register Func<T, IStorageOperations> in AddMartenStore<T>() so JasperFx's
  UsingStore<TStore>() can open a lightweight session without a Marten dependency
- Remove AncillaryStore phantom-struct types; keep only the Lazy<TStore> overload
- Update test to use the clean .UsingStore<IProductStore>() syntax
@jeremydmiller
Copy link
Copy Markdown
Member

Just moved this into a different PR

jeremydmiller added a commit that referenced this pull request Apr 28, 2026
…4301) (#4313)

* Add UsingStore<T> declarative enrichment from ancillary stores (#4300)

Plumbs IServiceProvider through StoreOptions and IStorageOperations.Services
so the JasperFx.Events 1.31 EntityStep<TEntity>.UsingStore<TStore>() built-in
can resolve ancillary IDocumentStore types from DI at enrichment time. Adds
a Lazy<TStore> overload (AncillaryStoreEnrichmentExtensions.UsingStore) for
projections that already hold a Lazy<> reference, plus an end-to-end test
demonstrating projection enrichment from a separate ancillary store.

Supersedes #4301 — rebased onto current master (JasperFx 1.28.1 /
JasperFx.Events 1.31.0). Original work by Anne Erdtsieck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reset Target.Random in TargetSchemaFixture for deterministic LINQ data

LinqTests.Acceptance shares a Target[] dataset built from the static
Target.GenerateRandomData helper. That helper consumes a process-wide
Random(67) which other tests in the same assembly may have already
advanced. When the LinqTests fixture happened to land on an unlucky
slice of that sequence, we'd see two related flakes:

- select_clauses: SelectTransform.Compare picks the first Target with
  StringArray.Length > 0, NumberArray.Length > 0, and Inner != null.
  If no document matched, target was null and line 27 NRE'd through
  every select_clauses theory.
- take_and_skip: OrderBy(x => x.Long).Skip(N).Take(M) compared against
  Postgres ORDER BY which is unstable on Long ties; non-deterministic
  data occasionally produced a tie.

Calling Target.ResetRandomSeed() in the fixture constructor before
generating Documents/FSharpDocuments makes the dataset stable seed-67
data: 0 Long-value collisions across 1000 docs and 3 select_clauses-
eligible Targets. Continues the flake-stabilization work from #4310 /
#4311.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Anne Erdtsieck <anne.erdtsieck@topicus.nl>
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.

Feature: Declarative ancillary store enrichment via UsingStore<T>() in the enrich API

2 participants