Skip to content

F# codegen: EF Core slice — runnable sample + compile-gate (GH-2969)#2983

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-2969-fsharp-sample-efcore
May 29, 2026
Merged

F# codegen: EF Core slice — runnable sample + compile-gate (GH-2969)#2983
jeremydmiller merged 1 commit into
mainfrom
feat-2969-fsharp-sample-efcore

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Part of the F# code-generation audit (#2969). After the foundation + Phase A/B/C work proved Wolverine's core/HTTP/saga frames emit F#, this is the first store-specific slice: EF Core — and the first runnable F# Wolverine sample, an explicit #2969 acceptance criterion.

EF Core frame F# emit

  • EnrollDbContextInTransaction → a task { } body: enroll-in-outbox, a conditional BeginTransactionAsync (if isNull … then let! _ = …; ()), and a try … with wrapping Next. reraise() is illegal inside a computation-expression try/with (FS0413), so the handler rolls back and rethrows via ExceptionDispatchInfo.Capture(ex).Throw() — preserving the original stack trace, the exact semantics of C# throw;.
  • CommitEfCoreEnvelopeTransactiondo! envelopeTransaction.CommitAsync(cancellation).

Generated F# (from the gate):

let efCoreEnvelopeTransaction = Wolverine.EntityFrameworkCore.Internals.EfCoreEnvelopeTransaction(itemsDbContext, context, _domainEventScraperIEnumerable)
do! context.EnlistInOutboxAsync(efCoreEnvelopeTransaction)
if isNull itemsDbContext.Database.CurrentTransaction then
    let! _ = itemsDbContext.Database.BeginTransactionAsync(cancellation)
    ()
try
    let outgoing1 = WolverineFSharpSample.CreateItemHandler.Handle(createItemCommand, itemsDbContext)
    do! context.EnqueueCascadingAsync(outgoing1)
    let! result_of_SaveChangesAsync = itemsDbContext.SaveChangesAsync(cancellation)
    do! efCoreEnvelopeTransaction.CommitAsync(cancellation)
with ex ->
    do! efCoreEnvelopeTransaction.RollbackAsync()
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex).Throw()

Runnable sample — src/Samples/WolverineFSharpSample

A real F# Wolverine app: F# Item/ItemsDbContext/CreateItemCommand/ItemCreated + a [<Transactional>] CreateItemHandler. Runs via dynamic codegen (references Wolverine.RuntimeCompilation + opts.UseRuntimeCompilation(), since core dropped the in-box compiler in #2876). Postgres-backed — Wolverine's EF Core outbox needs a durable message store, so the runnable sample is not infra-free (the static F# story is the compile-gate's job). Verified end-to-end against the docker-compose Postgres: prints Created an Item through the F# Wolverine + EF Core handler.

Compile-gate — src/Testing/Wolverine.EfCore.FSharp{Tests,Fixture}

Renders the sample's real CreateItemCommand chain to F# via the no-host HandlerGraphAssembleTypesGenerateFSharpCode path, then dotnet builds the checked-in fixture (with the FS0193/lock retry). Mirrors the Core/Http gates.

Wire-up

  • Sample + gate added to wolverine_fsharp.slnx.
  • fsharp.yml runs the EF Core gate as its own sequential step (concurrent nested fixture builds race on Wolverine.dll).

Dependency

Bumps JasperFx 2.2.5 → 2.2.7. 2.2.7 (#400) adds F# emit for the service-location frame surface the EF Core render needs — LazyServiceLocationFrame (the one this slice hit) plus the rest of the previously-throwing frames.

Verification

  • EF Core compile-gate green against published JasperFx 2.2.7.
  • dotnet build wolverine.slnx -c Release clean (per CLAUDE.md the slim build isn't sufficient).
  • Sample runs end-to-end on Postgres.

🤖 Generated with Claude Code

…H-2969)

First store-specific slice of the F# code-generation audit: a runnable F#
Wolverine + EF Core app and a compile-gate proving its handler chain emits
valid F# through Wolverine's static codegen path.

EF Core frame F# emit:
- EnrollDbContextInTransaction  -> task { } body: enroll-in-outbox, conditional
  BeginTransactionAsync (let! _ = ...; isNull guard), try/with around Next.
  reraise() is illegal inside a CE try/with (FS0413), so the handler rolls back
  and rethrows via ExceptionDispatchInfo.Capture(ex).Throw() (preserves the stack
  trace, the semantics of C# `throw;`).
- CommitEfCoreEnvelopeTransaction -> do! envelopeTransaction.CommitAsync(...).

Runnable sample (src/Samples/WolverineFSharpSample):
- F# Item/ItemsDbContext/CreateItemCommand/ItemCreated + a [<Transactional>]
  CreateItemHandler. Runs via dynamic codegen (references Wolverine.RuntimeCompilation
  + UseRuntimeCompilation, since core dropped the in-box compiler, GH-2876).
- Postgres-backed: the EF Core outbox needs a durable message store, so the sample
  is not infra-free (the *static* F# story is the compile-gate's job).

Compile-gate (src/Testing/Wolverine.EfCore.FSharp{Tests,Fixture}):
- Renders the sample's real CreateItemCommand chain to F# via the no-host
  HandlerGraph/AssembleTypes/GenerateFSharpCode path and dotnet-builds the fixture.

Wire-up: sample + gate added to wolverine_fsharp.slnx; fsharp.yml runs the EF gate
as its own sequential step. Bumps JasperFx 2.2.5 -> 2.2.7 (the service-location frame
surface the EF render needs: LazyServiceLocationFrame et al., #400).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 07c2ee8 into main May 29, 2026
24 checks passed
This was referenced Jun 1, 2026
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