We’ve had an undocumented until now API in Marten for years called EventProjectionScenario for declarative testing of Marten projections — somewhat based on the Scenario usage in our Alba library for ASP.Net Core testing. As part of some cleanup this week, I finally added some documentation and lifted that to where Polecat (Event Sourcing with SQL Server) can use it as well.
I’m more curious than anything to get some feedback here if anyone things this would be useful. After CritterWatch 1.0 lands, my attention is going to turn to the Critter Stack’s story for “Spec Driven Development,” and maybe this feature will be part of that.
Scripted Scenarios with EventProjectionScenario
For a more declarative way to test a projection end to end, Marten has a built-in scenario runner on IDocumentStore.Advanced that scripts a sequence of event appends and document assertions, then executes the whole sequence for you:
The scenario works with any projection lifecycle. If the store has any asynchronous projections registered, the scenario quietly spins up a projection daemon, waits for it to catch up after each batch of appended events, and shuts it down afterward — your test code looks identical either way.
A few things to know about how a scenario executes:
The Append() / StartStream() / AppendEvents() calls queue work — nothing touches the database until the scenario executes. The StartStream() overloads that generate their own stream id return that Guid so you can capture it for later assertions.
Consecutive appends are batched into a single commit; the pending work is saved whenever the next step is an assertion, and once more at the end of the scenario.
Assertion steps run against a query session after the projected data is up to date. DocumentShouldExist<T>() and DocumentShouldNotExist<T>() cover the common cases, and AssertAgainstProjectedData() is the general purpose hook for anything else.
If an action step fails, the scenario stops immediately — the remaining steps would be running against a state nobody intended. Failed assertions accumulate instead, and everything is reported at the end in a single ProjectionScenarioException that lists each step and what went wrong. Assertion failures inside the aggregate are typed as ProjectionScenarioAssertionException so tooling can tell them apart from infrastructure failures.
WARNING
By default the scenario deletes all event data plus the storage for every registered projection before it runs, so each scenario starts from a clean slate. Only use this feature against a test database! To run a scenario on top of existing data instead, set scenario.DeleteExistingData = false.
// Keep any existing event/projection data (the default is to wipe it)
scenario.DeleteExistingData=false;
// Apply the whole scenario to one tenant when using multi-tenancy
scenario.TenantId="tenant1";
// Maximum time to wait for async projections to catch up
// after each batch of events. The default is 30 seconds
scenario.Timeout=5.Seconds();
// ... queue up appends and assertions
});
We’ll also have the “Projection Stepper” feature in CritterWatch that will allow you to step through a series of events to see how a projection creates and modifies its view event by event. That functionality is part of the CritterWatch user interface, but also exposed via an MCP endpoint on CritterWatch for easy access for AI agents building and troubleshooting Critter Stack applications using Event Sourcing.
In many .NET systems, writing a web service that returns query results means some combination of:
Query data from EF Core — which is going to do who knows what to build up SQL, execute that, then spend some time materializing the raw database results into .NET objects
Since we’ve all been taught for years that it’s harmful to expose our internal entity shapes to the outside world, maybe you’re running the results through some kind of object to object mapping to a different DTO shape
Finally, after all the database querying and object mapping, you’ll finally use a JSON serializer to write results to the HTTP response stream
Whew. That’s a non-trivial amount of your time (or AI tokens) and a significant amount of runtime overhead with all the transformations and thrashing your memory with all the object allocations involved.
Now let’s talk about some capabilities in Marten and Polecat to sidestep the mass majority of that overhead in some cases — but first, I do need to say that if you’re using Event Sourcing, the persisted data in a Marten or Polecat database for query models is purpose built for clients as it is. No extra mapping necessary. In a way, the “AutoMapper” activity happens directly in projections for a system using Event Sourcing.
If you are building HTTP services on top of Marten or Polecat, both of these tools have a “JSON Streaming” feature that can be used to build very fast web services by writing the raw JSON stored in PostgreSQL or SQL Server directly to the HTTP response for the most efficient possible HTTP web services in the read side of a CQRS architecture.
Core team member Anne Erdtsieck just made some a bunch of extensions to Marten and Polecat‘s ability to stream the raw, persisted JSON data stored in the database straight to HTTP responses, and that makes now a good time to show off what we have.
For Minimal API endpoints (and for frameworks like Wolverine.Http that dispatch any IResult return value), Marten.AspNetCore (Polecat.AspNetCore has similar support) ships seven typed result wrappers that carry the streaming behavior above as endpoint return values while also contributing correct OpenAPI metadata:
Type
Source
Response shape
404 on miss?
StreamOne<T>
IQueryable<T> — regular Marten document query
Single T
yes
StreamMany<T>
IQueryable<T> — regular Marten document query
JSON array T[]
no (empty array = 200)
StreamAggregate<T>
IDocumentSession + stream id — event-sourced
Single T
yes
StreamPaged<T>
IQueryable<T> — regular Marten document query
Paged JSON envelope
no (empty page = 200)
StreamPagedByCursor<T>
IQueryable<T> (with OrderBy/ThenBy)
no (empty array = 200)
StreamEventState
IQuerySession + stream id — event stream
Single StreamStateResponse
yes
StreamEvents
IQuerySession + stream id — event stream
JSON array EventResponse[]
yes (configurable)
Each type implements both IResult (so ASP.NET Minimal API dispatches it via ExecuteAsync) and IEndpointMetadataProvider (so Swashbuckle, NSwag, and the built-in OpenAPI generator see the right response shape), while delegating the actual body write to WriteSingle/WriteArray/WriteLatest/WriteStreamState/WriteEvents. Returning one from an endpoint is a concise, typed alternative to writing the HTTP handshake manually.
Returns 200 application/json with the document JSON on a hit, 404 on a miss. Content-Length and Content-Type are set automatically, matching the behavior of WriteSingle<T>.
pageNumber is 1-based. totalItemCount and pageCount are computed from a count(*) OVER() window function added to the same SQL query that fetches the page, so the whole response — count and documents both — comes from a single database round trip. Documents inside items are streamed as raw, already-persisted JSON, without a deserialize/serialize step. An empty page still returns 200 with totalItemCount: 0, pageCount: 0, and an empty items array — never a 404.
Internally, StreamPaged<T> delegates to the IQueryable<T>.StreamPagedJsonArray() extension method described in the Paging docs, which can also be used directly (e.g. from an MVC controller action) instead of through the IResult wrapper.
Returns 200 application/json with the JSON of the latest projected aggregate state, or 404 if no stream exists. A constructor overload accepts string ids for stores configured with string-keyed streams.
StreamEventState — event stream metadata
Writes the high level metadata of a single event stream — Marten’s StreamState — as JSON, or 404 when the stream does not exist:
app.MapGet("/minimal/order/{id:guid}/state",
(Guidid, IQuerySessionsession)
=>newStreamEventState(session, id));
A constructor overload accepts a string stream key for stores configured with string-keyed streams.
The response body is a StreamStateResponse, not StreamState itself. StreamState.AggregateType is a System.Type, and System.Text.Json refuses to serialize those outright (Serialization and deserialization of 'System.Type' instances is not supported), so the aggregate type is projected down to its simple name in AggregateTypeName:
{
"id":"0198e1b4-5b1c-7a1e-9a3f-2f2f5b6c7d8e",
"key":null,
"version":2,
"aggregateTypeName":"Order",
"lastTimestamp":"2026-07-26T09:41:02.113Z",
"created":"2026-07-26T09:41:02.098Z",
"isArchived":false
}
StreamEvents — raw events of a stream 9.20
Writes the raw events of a single event stream as a JSON array:
app.MapGet("/minimal/order/{id:guid}/events",
(Guidid, IQuerySessionsession)
=>newStreamEvents(session, id));
StreamEvents carries the same optional version, timestamp, and fromVersion filters as FetchStreamAsync(), and there is a string stream key overload as well.
Elements are EventResponse, not IEvent itself — IEvent.EventType is a System.Type and hits the same System.Text.Json wall as above. Use eventTypeName, Marten’s stable event type alias, to discriminate event types on the client. The assembly qualified .NET type name (DotNetTypeName) is deliberately left off the wire:
FetchStream yields an empty list both for a stream that does not exist and for a filter that excludes every event, and the two cannot be told apart. StreamEvents therefore exposes an OnEmptyStatus that defaults to 404, matching the other single-resource results. Set it to 200 when running off the end of a stream is expected rather than exceptional — paging forward with fromVersion, for example:
// Paging forward through a stream: running off the end is expected, not a 404
For non basketball fans, the NCAA Tournament championship game broadcasts end each year with a highlight montage to a cheesy song called “One Shining Moment” that’s one of my favorite things to watch each year.
The Critter Stack community is pretty much always busy, but we were able to make some releases to Marten, Polecat, and Wolverine yesterday and today that dropped our open issue counts on GitHub to the lowest number in a decade. That’s bug fixes, some long overdue structural improvements, quite a few additions to the documentation, new features, and some quiet enablement of near term improvements in CritterWatch and our AI development strategy.
Wolverine 5.28.0 Released
We’re happy to announce Wolverine 5.28.0, a feature-packed release that significantly strengthens both the messaging and HTTP sides of the framework. This release includes major new infrastructure for transport observability, powerful new Wolverine.HTTP capabilities bringing closer parity with ASP.NET Core’s feature set, and several excellent community contributions.
Last week I took some time to do a “gap analysis” of Wolverine.HTTP against Minimal API and MVC Core for missing features and did a similar exercise of Wolverine’s asynchronous messaging support against other offerings in the .NET and Java world. This release actually plugs most of those gaps — albeit with just documentation in many cases.
Highlights
🔍 Transport Health Checks
This has been one of our most requested features. Wolverine now provides built-in health check infrastructure for all message transports — RabbitMQ, Kafka, Azure Service Bus, Amazon SQS, NATS, Redis, and MQTT. The new WolverineTransportHealthCheck base class reports point-in-time health status including connection state and, where supported, broker queue depth — critical for detecting the “silent failure” scenario where messages are piling up on the broker but aren’t being consumed (a situation we’ve seen in production with RabbitMQ).
Health checks integrate with ASP.NET Core’s standard IHealthCheck interface, so they plug directly into your existing health monitoring infrastructure.
This was built specifically for CritterWatch integration. I should also point out that CritterWatch is now able to kickstart the “silent failure” issues where Marten/Polecat projections claim to be running, but not advancing and messaging listeners who appear to be active but also aren’t actually receiving messages.
🔌 Wire Tap (Message Auditing)
Implementing the classic Enterprise Integration Patterns Wire Tap, this feature lets you record a copy of every message flowing through configured endpoints — without affecting the primary processing pipeline. It’s ideal for compliance logging, analytics, or debugging.
opts.ListenToRabbitQueue("orders")
.UseWireTap();
Implement the IWireTap interface with RecordSuccessAsync() and RecordFailureAsync() methods, and Wolverine handles the rest. Supports keyed services for different implementations per endpoint.
This feature is meant to be a new type of “declarative invariant” that will enable Critter Stack systems to be more efficient. If this is used with other declarative persistence helpers in the same HTTP endpoint or message handler, Wolverine is able to opt into Marten’s batch querying for more efficient code.
New [DocumentExists<T>] and [DocumentDoesNotExist<T>] attributes let you declaratively guard handlers with Marten document existence checks. Wolverine generates optimized middleware at compile time — no manual boilerplate needed:
A community contribution that adds first-class support for Confluent Schema Registry serialization with Kafka topics. Both JSON Schema and Avro (for ISpecificRecord types) serializers are included, with automatic schema ID caching and the standard wire format (magic byte + 4-byte schema ID + payload).
This release brings a wave of HTTP features that close the gap with vanilla ASP.NET Core while maintaining Wolverine’s simpler programming model:
Response Content Negotiation
New ConnegMode configuration with Loose (default, falls back to JSON) and Strict (returns 406 Not Acceptable) modes. Use the [Writes] attribute to declare supported content types and [StrictConneg] to enforce strict matching per endpoint.
This is orthogonal to Wolverine’s error handling policies.
Handler and middleware methods named OnException or OnExceptionAsync are now automatically wired as exception handlers, ordered by specificity. Return ProblemDetails, IResult, or HandlerContinuation to control the response:
public static ProblemDetails OnException(OrderNotFoundException ex)
{
return new ProblemDetails { Status = 404, Detail = ex.Message };
Direct integration with ASP.NET Core’s output caching middleware via the [OutputCache] attribute on endpoints, supporting policy names, VaryByQuery, VaryByHeader, and tag-based invalidation.
Form endpoints automatically require antiforgery validation. Use [ValidateAntiforgery] to opt in non-form endpoints or [DisableAntiforgery] to opt out. Global configuration available via opts.RequireAntiforgeryOnAll().
Documentation and examples for Server-Sent Events and streaming responses using ASP.NET Core’s Results.Stream(), fully integrated with Wolverine’s service injection.
Marten 8.29.0 Release — Performance, Extensibility, and Bug Fixes
Marten 8.29.0 shipped yesterday with a packed release: a new LINQ operator, event enrichment for EventProjection, major async daemon performance improvements, the removal of the FSharp.Core dependency, and several important bug fixes for partitioned tables.
New Features
OrderByNgramRank — Sort Search Results by Relevance
You can now sort NGram search results by relevance using the new OrderByNgramRank() LINQ operator:
varresults=awaitsession
.Query<Product>()
.Where(x=>x.Name.NgramSearch("blue shoes"))
.OrderByNgramRank(x=>x.Name, "blue shoes")
.ToListAsync();
This generates ORDER BY ts_rank(mt_grams_vector(...), mt_grams_query(...)) DESC under the hood — no raw SQL needed.
EnrichEventsAsync for EventProjection
The EnrichEventsAsync hook that was previously only available on aggregation projections (SingleStreamProjection, MultiStreamProjection) is now available on EventProjection too. This lets you batch-load reference data before individual events are processed, avoiding N+1 query problems:
ConfigureNpgsqlDataSourceBuilder — Plugin Registration for All Data Sources
A new ConfigureNpgsqlDataSourceBuilder API on StoreOptions ensures Npgsql plugins like UseVector(), UseNetTopologySuite(), and UseNodaTime() are applied to everyNpgsqlDataSource Marten creates — including tenant databases in multi-tenancy scenarios:
This is the foundation for external PostgreSQL extension packages (PgVector, PostGIS, etc.) to work correctly across all tenancy modes.
And by the way, JasperFx will be releasing formal Marten support for pgvector and PostGIS in commercial add ons very soon.
Performance Improvements
Opt-in Event Type Index for Faster Projection Rebuilds
If your projections filter on a small subset of event types and your event store has millions of events, rebuilds can time out scanning through non-matching events. A new opt-in composite index solves this:
opts.Events.EnableEventTypeIndex=true;
This creates a (type, seq_id) B-tree index on mt_events, letting PostgreSQL jump directly to matching event types instead of sequential scanning.
And as always, remember that adding more indexes can slow down inserts, so use this judiciously.
Adaptive EventLoader
TL;DR: this helps make the Async Daemon be more reliable in the face of unexpected usage and more adaptive to get over unusual errors in production usage.
Even without the index, the async daemon now automatically adapts when event loading times out. It falls back through progressively simpler strategies — skip-ahead (find the next matching event via MIN(seq_id)), then window-step (advance in 10K fixed windows) — and resets when events flow normally. No configuration needed.
See the expanded tuning documentation for guidance on when to enable the index and how to diagnose slow rebuilds.
FSharp.Core Dependency Removed
Marten no longer has a compile-time dependency on FSharp.Core. F# support still works — if your project references FSharp.Core (as any F# project does), Marten detects it at runtime via reflection. This unblocks .NET 8 users who were stuck on older Marten versions due to the FSharp.Core 9.0.100 requirement.
If you use F# types with Marten (FSharpOption, discriminated union IDs, F# records), everything continues to work unchanged. The dependency just moved from Marten’s package to your project.
Bug Fixes
Partitioned Table Composite PK in Update Functions (#4223)
The generated mt_update_* PostgreSQL function now correctly uses all composite primary key columns in its WHERE clause. Previously, for partitioned tables with a PK like (id, date), the update only matched on id, causing duplicate key violations when multiple rows shared the same ID with different partition keys.
Long Identifier Names (#4224)
Auto-discovered tag types with long names (e.g., BootstrapTokenResourceName) no longer cause PostgresqlIdentifierTooLongException at startup. Generated FK, PK, and index names that exceed PostgreSQL’s 63-character limit are now deterministically shortened with a hash suffix.
This has been longstanding problem in Marten, and we probably should have dealt with this years ago:-(
EF Core 10 Compatibility (#4225)
Updated Weasel to 8.12.0 which fixes MissingMethodException when using Weasel.EntityFrameworkCore with EF Core 10 on .NET 10.
Some time in the last couple weeks I wrote a blog post about my experiences so far with Claude assisted developement where I tried to say that you absolutely have to carefully review what your AI tools are doing because they can take short cuts. So, yeah, I should do that even more closely.
Polecat 2.0.1 is using the SQL Server 2025 native JSON type correctly now, and the database migrations are now all done with the underlying Weasel library that enables Polecat to play nicely with all of the Critter Stack command line support for migrations.
We put on another Critter Stack live stream today to give a highlight tour of the multi-tenancy features and support across the entire stack. Long story short, I think we have by far and away the most comprehensive feature set for multi-tenancy in the .NET ecosystem, but I’ll let you judge that for yourself:
The Critter Stack provides comprehensive multi-tenancy support across all three tools — Marten, Wolverine, and Polecat — with tenant context flowing seamlessly from HTTP requests through message handling to data persistence. Here’s some links to various bits of documentation and some older blog posts at the bottom as well.
Marten (PostgreSQL)
Marten offers three tenancy strategies for both the document database and event store:
Conjoined Tenancy — All tenants share tables with automatic tenant_id discrimination, cross-tenant querying via TenantIsOneOf() and AnyTenant(), and PostgreSQL LIST/HASH partitioning on tenant_id (Document Multi-Tenancy, Event Store Multi-Tenancy)
Database per Tenant — Four strategies ranging from static mapping to single-server auto-provisioning, master table lookup, and runtime tenant registration (Database-per-Tenant Configuration)
Sharded Multi-Tenancy with Database Pooling — Distributes tenants across a pool of databases using hash, smallest-database, or explicit assignment strategies, combining conjoined tenancy with database sharding for extreme scale (Database-per-Tenant Configuration)
Global Streams & Projections — Mix globally-scoped and tenant-specific event streams within a conjoined tenancy model (Event Store Multi-Tenancy)
Wolverine (Messaging, Mediator, and HTTP)
Wolverine propagates tenant context automatically through the entire message processing pipeline:
Handler Multi-Tenancy — Tenant IDs tracked as message metadata, automatically propagated to cascaded messages, with InvokeForTenantAsync() for explicit tenant targeting (Handler Multi-Tenancy)
HTTP Tenant Detection — Built-in strategies for detecting tenant from request headers, claims, query strings, route arguments, or subdomains (HTTP Multi-Tenancy)
Marten Integration — Database-per-tenant or conjoined tenancy with automatic IDocumentSession scoping and transactional inbox/outbox per tenant database (Marten Multi-Tenancy)
Polecat Integration — Same database-per-tenant and conjoined patterns for SQL Server (Polecat Multi-Tenancy)
EF Core Integration — Multi-tenant transactional inbox/outbox with separate databases and automatic migrations (EF Core Multi-Tenancy)
RabbitMQ per Tenant — Map tenants to separate virtual hosts or entirely different brokers (RabbitMQ Multi-Tenancy)
Database per Tenant — Dedicated SQL Server database per tenant with independent schema management and async daemon processing (Database-per-Tenant Configuration)
As anybody knows who follows the Critter Stack on our Discord server, I’m uncomfortable with the rapid pace of releases that we’ve sustained in the past couple quarters and I think I would like the release cadence to slow down. However, open issues and pull requests feel like money burning a hole in my pocket, and I don’t letting things linger very long. Our rapid cadence is somewhat driven by JasperFx Software client requests, some by our community being quite aggressive in contributing changes, and our users finding new issues that need to be addressed. While I’ve been known to be very unhappy with feedback saying that our frequent release cadence must be a sign of poor quality, I think our community seems to mostly appreciate that we move relatively fast. I believe that we are definitely innovating much faster and more aggressively than any of the other asynchronous messaging tools in the .NET space, so there’s that. Anyway, enough of that, here’s a rundown of the new releases today.
It’s been a busy week across the Critter Stack! We shipped coordinated releases today across all five projects: Marten 8.27, Wolverine 5.25, Polecat 1.5, Weasel 8.11.1, and JasperFx 1.21.1. Here’s a rundown of what’s new.
Marten 8.27.0
Sharded Multi-Tenancy with Database Pooling
For teams operating at extreme scale — we’re talking hundreds of billions of events — Marten now supports a sharded multi-tenancy model that distributes tenants across a pool of databases. Each tenant gets its own native PostgreSQL LIST partition within a shard database, giving you the isolation benefits of per-tenant databases with the operational simplicity of a managed pool.
Configuration is straightforward:
opts.MultiTenantedWithShardedDatabases(x =>
{
// Connection to the master database that holds the pool registry
x.ConnectionString = masterConnectionString;
// Schema for the registry tables in the master database
x.SchemaName = "tenants";
// Seed the database pool on startup
x.AddDatabase("shard_01", shard1ConnectionString);
x.AddDatabase("shard_02", shard2ConnectionString);
x.AddDatabase("shard_03", shard3ConnectionString);
x.AddDatabase("shard_04", shard4ConnectionString);
// Choose a tenant assignment strategy (see below)
x.UseHashAssignment(); // this is the default
});
Calling MultiTenantedWithShardedDatabases() automatically enables conjoined tenancy for both documents and events, with native PG list partitions created per tenant.
Three tenant assignment strategies are built-in:
Hash Assignment (default) — deterministic FNV-1a hash of the tenant ID. Fast, predictable, no database queries needed. Best when tenants are roughly equal in size.
Smallest Database — assigns new tenants to the database with the fewest existing tenants. Accepts a custom IDatabaseSizingStrategy for balancing by row count, disk usage, or any other metric.
Explicit Assignment — you control exactly which database hosts each tenant via the admin API.
The admin API lets you manage the pool at runtime: AddTenantToShardAsync, AddDatabaseToPoolAsync, MarkDatabaseFullAsync — all with advisory-locked concurrent safety.
Bulk COPY Event Append for High-Throughput Seeding
For data migrations, test fixture setup, load testing, or importing events from external systems, Marten now supports a bulk COPY-based event append that uses PostgreSQL’s COPY ... FROM STDIN BINARY for maximum throughput:
// Build up a list of stream actions with events
var streams = new List<StreamAction>();
for (int i = 0; i < 1000; i++)
{
var streamId = Guid.NewGuid();
var events = new object[]
{
new OrderPlaced(streamId, "Widget", 5),
new OrderShipped(streamId, $"TRACK-{i}"),
new OrderDelivered(streamId, DateTimeOffset.UtcNow)
};
streams.Add(StreamAction.Start(store.Events, streamId, events));
}
// Bulk insert all events using PostgreSQL COPY for maximum throughput
await store.BulkInsertEventsAsync(streams);
This supports all combinations of Guid/string identity, single/conjoined tenancy, archived stream partitioning, and metadata columns. When using conjoined tenancy, a tenant-specific overload is available:
FetchForWriting now auto-discovers natural keys without requiring an explicit projection registration, and works correctly with strongly typed IDs combined with UseIdentityMapForAggregates
Compiled queries using IsOneOf with array parameters now generate correct SQL
EF Core OwnsOne().ToJson() support (via Weasel 8.11.1) — schema diffing now correctly handles JSON column mapping when Marten and EF Core share a database
Thanks to @erdtsieck for fixing duplicate codegen when using secondary document stores!
Wolverine 5.25.0
This is a big release with 12 PRs merged — a mix of bug fixes, new features, and community contributions.
MassTransit and NServiceBus Interop for Azure Service Bus Topics
Previously, MassTransit and NServiceBus interoperability was only available on Azure Service Bus queues. With 5.25, you can now interoperate on ASB topics and subscriptions too — making it much easier to migrate incrementally or coexist with other .NET messaging frameworks:
// Publish to a topic with NServiceBus interop
opts.PublishAllMessages().ToAzureServiceBusTopic("nsb-topic")
.UseNServiceBusInterop();
// Listen on a subscription with MassTransit interop
opts.ListenToAzureServiceBusSubscription("wolverine-sub")
.FromTopic("wolverine-topic")
.UseMassTransitInterop(mt => { })
.DefaultIncomingMessage<ResponseMessage>().UseForReplies();
Both UseMassTransitInterop() and UseNServiceBusInterop() are available on AzureServiceBusTopic (for publishing) and AzureServiceBusSubscription (for listening). This is ideal for brownfield scenarios where you’re migrating services one at a time and need different messaging frameworks to talk to each other through shared ASB topics.
Other New Features
Handler Type Naming for Conventional Routing — NamingSource.FromHandlerType names listener queues after the handler type instead of the message type, useful for modular monolith scenarios with multiple handlers per message
Enhanced WolverineParameterAttribute — new FromHeader, FromClaim, and FromMethod value sources for binding handler parameters to HTTP headers, claims, or static method return values
Full Tracing for InvokeAsync — opt-in InvokeTracingMode.Full emits the same structured log messages as transport-received messages, with zero overhead in the default path
Configurable SQL transport polling interval — thanks to new contributor @xwipeoutx!
SQL Server saga storage now supports nvarchar identity columns (thanks @kakins!)
Polecat 1.5.0
Polecat — the Critter Stack’s newer, lighter-weight event store option — had a big jump from 1.2 to 1.5:
net9.0 support and CI workflow
SingleStreamProjection<TDoc, TId> with strongly-typed ID support
Auto-discover natural keys for FetchForWriting
Conjoined tenancy support for DCB tags and natural keys
Fix for FetchForWriting with UseIdentityMapForAggregates and strongly typed IDs
Weasel 8.11.1
EF Core OwnsOne().ToJson() support — Weasel’s schema diffing now correctly handles EF Core’s JSON column mapping, preventing spurious migration diffs when Marten and EF Core share a database
JasperFx 1.21.1 / JasperFx.Events 1.24.1
Skip unknown flags when AutoStartHost is true — fixes an issue where unrecognized CLI flags would cause errors during host auto-start
Retrofit IEventSlicer tests
Upgrading
All packages are available on NuGet now. The Marten and Wolverine releases are fully coordinated — if you’re using the Critter Stack together, upgrade both at the same time for the best experience.
As always, please report any issues on the respective GitHub repositories and join us on the Critter Stack Discord if you have questions!
If you’re already familiar with Marten and Wolverine, this is all old news except for the part where we’re using SQL Server. If you’re brand new to the “Critter Stack,” Event Sourcing, or CQRS, hang around! And just so you know, JasperFx Software is completely ready to support our clients using Polecat.
With the advent of Polecatgoing 1.0 last week, you now have a robust solution for Event Sourcing using SQL Server 2025 as the backing store. If you’re reading this, you’re surely involved in software development and that means that your job at some point has been dictated by some kind of issue tracking tool, so let’s use that as our example system and pretend we’re creating an incident tracking system for our help desk folks as shown below:
To get started, I’m a fan of using the Event Storming technique to identify some of the meaningful events we should capture in our system and start to identify possible commands within our system:
Having at least some initial thoughts about the shape of our system, let’s start a new web service project in .NET with:
dotnet new webapi
Then add both Polecat (for persistence) and Wolverine (for both HTTP endpoints and asynchronous messaging) with:
dotnet add package WolverineFx.Polecat
dotnet add package WolverineFx.Http
And now, let’s jump into our Program file to wire up Polecat to an existing SQL Server database and configure Wolverine as well:
// and will soon feed quite a bit of AI assisted development as well
returnawaitapp.RunJasperFxCommands(args);
// For test bootstrapping in case you want to work w/
// more than one system at a time
publicpartialclassProgram
{
}
Our events are just going to be some immutable records like this:
publicrecordLogIncident(
GuidCustomerId,
ContactContact,
stringDescription,
GuidLoggedBy
);
publicrecordCategoriseIncident(
IncidentCategoryCategory,
GuidCategorisedBy,
intVersion
);
publicrecordCloseIncident(
GuidClosedBy,
intVersion
);
It’s not mandatory to use immutable types, but you might as well and it’s just idiomatic.
Let’s start with our LogIncident use case and build out an HTTP endpoint that creates a new “event stream” for events related to a single, logical Incident:
Polecat does support “Dynamic Consistency Boundary” event sourcing as well, but that’s not where I think most people should start, and I’ll get to that in a later post I keep putting off…
With some help from Alba, another JasperFx supported library, we can write both unit tests for the business logic (such as it is) and do an end to end test through the HTTP endpoint like this:
Now, to build out a command handler for potentially categorizing an event, we’ll need to:
Know the current state of the logical Incident by rolling up the events into some kind of representation of the state so that we can “decide” which if any events should be appended at this time. In Event Sourcing terms, I’d refer to this as the “write model.”
The command type itself
Validation logic for the input
Like I said earlier, decide which events should be published
Do some metadata correlation for observability. It’s not obvious from the code, but in the sample below Wolverine & Marten are tracking the events captured against the correlation id of the current HTTP request
Establish transactional boundaries, including any outbound messaging that might be taking place in response to the events that are being appended. This is something that Wolverine does for Polecat (and Marten) in command handlers. This includes the transactional outbox support in Wolverine.
Create protections against concurrent writes to any given Incident stream, which Wolverine and Polecat do for you in the next endpoint by applying optimistic concurrency checks to guarantee that no other thread changed the Incident since this CategoriseIncident command was issued by the caller
That’s actually quite a bit of responsibility for the command handler, but not to worry, Wolverine and Polecat are going to keep your code nice and simple. Hopefully even a pure function “Decider” for the business logic in many cases. Before I get into the command handler, here’s what the “projection” that gives us the current state of the Incident by applying events:
publicclassIncident
{
publicGuidId { get; set; }
// Polecat will set this itself for optimistic concurrency
Polecat is now completely supported by JasperFx Software and automatically part of any existing and future support agreements through our existing plans.
Polecat was released as 1.0 this past week (with 1.1 & now 1.2 coming soon). Let’s call it what it is, Polecat is a port of (most of) Marten to target SQL Server 2025 and SQL Server’s new JSON data type. For folks not familiar with Marten, Polecat is in one library:
And while Polecat is brand spanking new, it comes out of the gate with the decade old Marten pedigree and its own Wolverine integration for CQRS usage. I’m confident in saying Polecat is now the best technical option for using Event Sourcing with SQL Server in the .NET ecosystem.
And of course, if you’re a shop with deep existing roots into EF Core usage, Polecat also comes with projection support to EF Core, so Polecat can happily coexist with EF Core in the same systems.
Alright, let’s just into a quick start. First, let’s say you’ve started a brand new .NET project through dotnet run webapi and you’ve added a reference to Polecat through Nuget (and you have a running SQL Server 2025 instance handy too of course!). Next, let’s start with the inevitable AddPolecat() usage in your Program file:
builder.Services.AddPolecat(options=>
{
// Connection string to your SQL Server 2025 database
For folks used to EF Core, I should point out that Polecat has its own “it just works” database migration subsystem that in the default development mode will happily make sure that all necessary database tables, views, and functions are exactly as they should be at runtime so you don’t have to fiddle with database migrations when all you want to do is just get things done.
While I initially thought that we’d mainly focus on the event sourcing support, we were also able to recreate the mass majority of Marten’s document database capabilities (including the “partial update” model, LINQ support, soft deletes, multi-tenancy, and batch updates for starters) as well if you’d only be interested in that feature set by itself.
Moving over to event sourcing instead, let’s say you’re into fantasy books like I am and you want to build a system to model the journeys and adventures of a quest in your favorite fantasy series. You might model some of the events in that system like:
And there’s much, much more of course, including everything you’d need to build real systems based on our 10 years and counting supporting Marten with PostgreSQL.
How is Polecat Different than Marten?
There are of course some differences besides just the database engine:
Polecat is using source generators instead of the runtime code generation that Marten does today
Polecat will only support System.Text.Json for now as a serialization engine
Polecat only supports the “Quick Append” option from Marten
There is no automatic dirty checking
No “duplicate fields” support so far, we’re going to reevaluate that though
Plenty of other technical baggage features I flat out didn’t want to support in Marten didn’t make the cut, but I can’t imagine anyone will miss any of that!
Summary
For over a decade people have been telling me that Marten would be more successful and adopted by more .NET shops if it only supported SQL Server in addition to or instead of PostgreSQL. While I’ve never really disagreed with that idea — and it’s impossible to really prove the counter factual anyway — there have always been real blockers in both SQL Server’s JSON support lagging far behind PostgreSQL and frankly the time commitment on my part to be able to attempt that work in the first place.
So what changed to enable this?
SQL Server 2025 added much better JSON support rivaling PostgreSQL’s JSONB type
We had already invested in pulling the basic event abstractions and projection support out of Marten and into a common library called JasperFx.Events as part of the Marten 8.0 release cycle and that work was always meant to be an enabler for what is now Polecat
Claude & Opus 4.5/4.6 turned out to be very, very good at grunt work
That second item had to this point been a near disaster in my mind because of how much work and time that took compared to the benefits and was the single most time consuming part of Polecat development. Let’s just say that I’m very relieved that that effort didn’t turn out to be a very expensive sunk cost for JasperFx!
I have no earthly idea how much traction Polecat will really get, but we’ve already had some interest from folks who have wanted to use Marten, but couldn’t get their .NET shop to adopt PostgreSQL. I’m hopeful!
It’s only a month since I’ve written an update on the Critter Stack roadmap, but it’s maybe worth some time on my part to update what I think the roadmap is now. The biggest change is the utter dominance of AI in the software development discourse and the fact that Claude usage has allowed us to chew through a shocking amount of backlog in the past 6 weeks. That’s probably also changed my own thinking about what should be next throughout this year.
First, some updates on what’s been added to the Critter Stack in just the last month:
We’ve added GroupJoin and GroupBy support to Marten (and Polecat’s) LINQ provider. This along with the “Composite Projection” feature we added earlier this year addresses the big concerns we had coming into this year for cross-document or cross-event stream views.
We also added first class EF Core Projections for Marten and Polecat as another option for creating denormalized views with Marten.
By the time you read this, we may very well have Polecat 1.0 out as well.
Short Term
The short term priority for myself and JasperFx Software is to deliver the CritterWatch MVP in a usable form by the end of March.
Marten, Wolverine, and even Polecat have no major new features planned for the short term and I think they will only get tactical releases for bug fixes and JasperFx client requests for a little while. And let me tell you, it feels *weird* to say that, but we’ve blown through a tremendous amount of the backlog so far in 2026.
Medium Term
Enhance CritterWatch until it’s the best in class monitoring tool for asynchronous messaging and event sourcing. Part of that will probably be adding quite a bit more functionality for development time as well.
For a JasperFx Software client, we’re doing PoC work on scaling Marten to be able to handle having several hundred billion events in a single system. I’m going to assume that this PoC will probably lead to enhancements in both Marten and Wolverine!
We’ll finally add some direct support to Marten for the PostGIS PostgreSQL extension
I’m a little curious to try to use the hstore extension with Marten as a possible way to optimize our new DCB support
Play with Pgvector and TimescaleDb in combination with Marten as some kind of vague “how can we say that Marten is even more awesome for AI?”
There’s going to be a new wave of releases later this year for Marten 9.0, Wolverine 6.0, and Polecat 2.0 that will mostly about performance optimizations and especially finding ways to optimize the cold start time of applications using these tools.
Babu and I (really all Babu so far) are going to be building a set of AI skills for using the Critter Stack tools that will be curated in a GitHub repository and available to JasperFx Software clients. I do not know what the full impact of AI tools are really going to be on software development, but I personally want to plan for the worst case that AI tools plus LLM-friendly documentation drastically reduces the demand for consulting and try to belatedly pivot JasperFx Software to being at least partially a product company.
Build tooling for spec driven development using the Critter Stack. I don’t have any details beyond “hey, wouldn’t that be cool?”. My initial thought is to play with Gherkin specifications that generates “best practices” Critter Stack code with the accompanying automated tests to boot.
One way or another, we’ll be building MCP support into the Critter Stack, but again, I don’t know anything more than “hey, wouldn’t that be cool?”
Long Term
Profit?
I’m playing with the idea of completely rebooting Storyteller as a new spec driven development tool. I have the Nuget rights to the “Storyteller” name and graphics from Khalid (a necessary requirement for any successful effort on my part), and I’ve always wanted to go back to it some day.