Stratara.EventSourcing.EntityFrameworkCore
4.3.1
Prefix Reserved
dotnet add package Stratara.EventSourcing.EntityFrameworkCore --version 4.3.1
NuGet\Install-Package Stratara.EventSourcing.EntityFrameworkCore -Version 4.3.1
<PackageReference Include="Stratara.EventSourcing.EntityFrameworkCore" Version="4.3.1" />
<PackageVersion Include="Stratara.EventSourcing.EntityFrameworkCore" Version="4.3.1" />
<PackageReference Include="Stratara.EventSourcing.EntityFrameworkCore" />
paket add Stratara.EventSourcing.EntityFrameworkCore --version 4.3.1
#r "nuget: Stratara.EventSourcing.EntityFrameworkCore, 4.3.1"
#:package Stratara.EventSourcing.EntityFrameworkCore@4.3.1
#addin nuget:?package=Stratara.EventSourcing.EntityFrameworkCore&version=4.3.1
#tool nuget:?package=Stratara.EventSourcing.EntityFrameworkCore&version=4.3.1
Stratara.EventSourcing.EntityFrameworkCore
Derived. The behaviour described here is specified under
openspec/specs/. Those specifications are the source; this page explains and illustrates them.
License: MIT.
EF Core persistence for the Stratara event-sourced stack — PostgreSQL flavoured via Npgsql. pgvector types are mapped for a model that has vector columns; a model without them runs on a plain PostgreSQL server, no vector extension needed. Bundles four previously-separate Stratara projects into one NuGet because they always ship together:
| Folder | Contents | Old csproj |
|---|---|---|
EntityFrameworkCore/ |
shared EF conventions, value generators, IDbContext / IReadDbContext / IWriteDbContext / ITenantScopedDbContext / IIdentityDbContext, UnitOfWork base, DefaultDbResolver, NpgsqlDbContextServiceCollectionExtensions, DbContextMigrationUtility |
Stratara.EntityFrameworkCore |
WriteStore/ |
WriteDbContext, WriteUnitOfWork, event-stream/snapshot/event-chain/command-audit/outbox repositories + entity configurations |
Stratara.EventSourcing.EntityFrameworkCore.WriteStore |
ReadStore/ |
ReadDbContext, ReadUnitOfWork, ProjectionsUnitOfWork, Tenant repository, projection entity configurations |
Stratara.EventSourcing.EntityFrameworkCore.ReadStore |
IdentityStore/ |
Generic ASP.NET Identity IdentityDbContext + marker |
Stratara.EventSourcing.EntityFrameworkCore.IdentityStore |
Everything lives under Stratara.EventSourcing.EntityFrameworkCore and its sub-namespaces (.WriteStore, .ReadStore, .IdentityStore, .Abstractions, .Conventions, .Extensions, .HealthChecks, .Migration). The DI extensions sit in Microsoft.Extensions.DependencyInjection, per the Microsoft convention, so they resolve without an extra using.
Why folded
WriteStore-without-ReadStore is not a real use case. EF conventions + value generators + UnitOfWork primitives are foundational for every other store. ASP.NET Identity glue follows the same EF Core conventions. Splitting into 4 NuGets that always ship together adds version-management noise (4× <PackageVersion> to keep in sync, transitive-resolution risk) without any consumer benefit.
If your application doesn't use ASP.NET Identity, simply don't reference IdentityDbContext-derived types — the rest of the package works without them.
Quick start
Derive your contexts from the generic base classes — the type argument is the context itself:
using Stratara.EventSourcing.EntityFrameworkCore.WriteStore;
using Stratara.EventSourcing.EntityFrameworkCore.ReadStore;
public sealed class MyAppWriteDbContext(DbContextOptions<MyAppWriteDbContext> options)
: WriteDbContext<MyAppWriteDbContext>(options);
public sealed class MyAppReadDbContext(DbContextOptions<MyAppReadDbContext> options)
: ReadDbContext<MyAppReadDbContext>(options);
Then register the Npgsql context factories and the write store. Each factory registration also
registers the unit of work over its context — IWriteUnitOfWork for the write side,
IProjectionsUnitOfWork / IReadUnitOfWork for the read side — unless the host registered its own.
The write-side unit of work also draws on ISessionContextProvider and ISecureJsonSerializer,
which AddSessionContext() and AddSecurity() provide (every worker composite applies both); with
those in place, this is all the store needs from the host:
// In your AppHost / Worker / Web project:
builder.Services
.AddNpgsqlWriteDbContextFactory<MyAppWriteDbContext>()
.AddNpgsqlReadDbContextFactory<MyAppReadDbContext>()
.AddWriteStore(builder.Configuration); // binds Stratara:EventSourcing options
Most hosts don't call these directly — the worker composites in Stratara.EventSourcing.WorkerDefaults
(AddCommandWorkerServices(), AddEventProjectionWorkerServices(), …) already compose AddWriteStore
for you. Reach for the explicit form when you build a host that none of the composites fit.
Renamed in 3.2.0, removed in 4.0.0: the write-side factory was
AddNpsqlWriteDbContextFactory(missing theg) through 3.1.x.AddNpgsqlWriteDbContextFactoryreplaced it in 3.2.0 and the old spelling stayed as an[Obsolete]alias until 4.0.0, which removed it. A host still on the old spelling adds theg; nothing else about the call changes.
Health checks
Two opt-in readiness checks plug into any IHealthChecksBuilder (they require the write store above to be registered):
builder.Services.AddHealthChecks()
.AddEventStoreHealthCheck() // write-side DB reachable?
.AddOutboxHealthCheck(degradedThreshold: 1_000, // outbox backlog depth
unhealthyThreshold: 10_000);
AddEventStoreHealthCheck()— probes write-store connectivity;Unhealthywhen the database cannot be reached.AddOutboxHealthCheck(degradedThreshold?, unhealthyThreshold?)— reports the pending outbox backlog under thependingdata key and escalates toDegraded/Unhealthywhen the count crosses the supplied thresholds (omit them to stay healthy while reachable).
Both are tagged ready by default, so they show up on a readiness endpoint rather than the liveness (live) one.
Dependencies
Stratara.Projections— for projection types used byProjectionsUnitOfWork.Stratara.Shared— for diagnostics + abstractions + resilience.Npgsql.EntityFrameworkCore.PostgreSQL,EFCore.NamingConventions,Microsoft.AspNetCore.Identity.EntityFrameworkCore,Microsoft.EntityFrameworkCore,Microsoft.Extensions.Diagnostics.HealthChecks,Pgvector.EntityFrameworkCore.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- EFCore.NamingConventions (>= 10.0.1)
- Microsoft.AspNetCore.Identity.EntityFrameworkCore (>= 10.0.11)
- Microsoft.EntityFrameworkCore (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.11)
- Microsoft.Extensions.Hosting (>= 10.0.11)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.3)
- Pgvector.EntityFrameworkCore (>= 0.3.0)
- Stratara.Projections (>= 4.3.1)
- Stratara.Shared (>= 4.3.1)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Stratara.EventSourcing.EntityFrameworkCore:
| Package | Downloads |
|---|---|
|
Stratara.Infrastructure
Infrastructure glue for the Stratara framework — authorization decorators, configuration providers, and DI composition helpers that wire Mediator, Outbox, Identity, and EF Core into a hosted app. |
|
|
Stratara.EventSourcing.WorkerDefaults
Worker-host wiring composites for the Stratara event-sourced stack. IHostApplicationBuilder extensions (AddBackendServices, AddCommandWorkerServices, AddHeavyCommandWorkerServices, AddEventProjectionWorkerServices, AddEventStreamHashWorkerServices, AddSagaWorkerServices, AddOutboxWorkerServices, and AddCommandServices, AddEventProjectionServices and AddSagaServices without the bus-fed workers) bundle the per-concern DI calls so each worker host opts in with one line. |
|
|
Stratara.Testing.EntityFrameworkCore
Spin up the real Stratara event-sourcing write stack (EventSource, aggregation, snapshots, the EF Core write store) against a shared in-memory SQLite database in one call — production code paths, no Postgres, no Docker. Builds on Stratara.Testing's in-memory doubles. |
|
|
Stratara.Orleans.EntityFrameworkCore
Entity Framework Core persistence for the Stratara Orleans execution model — the commit-order readers (native PostgreSQL and a portable per-partition counter), the projection checkpoint store and the store-schema additions they need. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.3.1 | 38 | 9/25/2026 |
| 4.3.0 | 251 | 9/23/2026 |
| 4.2.0 | 187 | 9/18/2026 |
| 4.1.1 | 121 | 9/16/2026 |
| 4.1.0 | 117 | 9/16/2026 |
| 4.0.4 | 919 | 9/14/2026 |
| 4.0.3 | 194 | 9/3/2026 |
| 4.0.2 | 616 | 9/3/2026 |
| 4.0.1 | 263 | 9/2/2026 |
| 4.0.0 | 181 | 8/31/2026 |
| 4.0.0-preview.1 | 78 | 8/31/2026 |
| 3.4.0 | 161 | 8/28/2026 |
| 3.3.0 | 137 | 8/25/2026 |
| 3.2.3 | 145 | 8/22/2026 |
| 3.2.2 | 140 | 8/14/2026 |
| 3.2.1 | 142 | 8/2/2026 |
| 3.2.0 | 161 | 7/18/2026 |
| 3.1.7 | 577 | 7/1/2026 |
| 3.1.6 | 572 | 6/22/2026 |
| 3.1.5 | 159 | 6/22/2026 |
A correctness release. A projection replay could apply a stream's later fact before its first and
then fail on every attempt, leaving the read models it had emptied empty; the two backfills that
prepare existing history for the Orleans execution model's commit-order readers took the same wrong
order. Beyond that, the setting store no longer needs declared settings, and a setting or permission
vocabulary can be declared in parts.
**Upgrading:**
- **If you replay projections, upgrade.** A store in which one save appended several versions of a
stream can hold sequence numbers that run against those versions, and a replay over it could not
complete. No entry is rewritten and no migration is needed.
- **If you implement `IEventStreamRepository` yourself,** override
`GetManyAfterSequenceInStreamOrderAsync` to give your replay the same guarantee; the default keeps
sequence order.
- **If you backfilled a store for a commit-order reader before this release,** the Orleans migration
guide gives the query that tells whether that history holds a stream out of version order, and how
to repair it.
- **A host that declared an empty `SettingCatalog` as a workaround can drop it.** A host that
registers a catalog through a factory and then calls `AddSettingCatalog` or `AddPermissionCatalog`
now fails at registration instead of replacing that catalog.
- No schema change.
### Added
- **`IEventStreamRepository.GetManyAfterSequenceInStreamOrderAsync`**: a range of the store in the order a
replay applies it — each stream's entries in version order, the streams interleaved as their sequence numbers
interleave them, and the range extended until it no longer ends between two versions of one stream. The default
implementation returns `GetManyAfterSequenceAsync`, sequence order, so a repository of your own compiles and
behaves as before; override the member to give your replay the guarantee.
### Fixed
- **A projection replay applies each stream in version order.** A save does not number its entries in version
order — EF Core chooses the statement order and the identity column numbers the rows as they arrive — and the
replay walked the store by sequence number, so it met a stream's later fact before its first. A projection that
reports a missing preceding fact then failed the batch on every attempt, and because a replay empties the read
models first, each attempt left the read side empty. On a store where many saves wrote several versions of one
stream, no replay could complete. The replay now reads through the new repository member; a batch can hold more
entries than `Projections:BatchSize`. No entry is rewritten and no migration is needed.
- **Both commit-order backfills keep each stream in version order.** `CommitTransactionIdBackfill` could end a
batch between two versions of one stream, stamping the later one with the earlier transaction.
`PartitionCounterBackfill` handed out positions in sequence order, so every save it positioned that the store
had numbered against its versions was read inverted by the portable reader. Both now extend a batch until it no
longer ends inside a stream, and the portable backfill positions each stream's entries in version order.
History backfilled with an earlier version is not revisited, because checkpoints stand on its commit records and
positions; the Orleans migration guide gives the query that tells whether a store holds such a stream.
- **The setting store works without declared settings.** `AddSettingStore<TContext>()` and
`AddSettingStoreFromContextFactory<TContext>()` resolved the `SettingCatalog` inside a factory, so a
host that registered the store and `AddStrataraErasure()` but declared no settings built, passed
`ValidateOnBuild` and every start-up check, and failed on its first erasure with *No service for type
`SettingCatalog` has been registered*. The store now registers an empty catalog when none is declared:
the store works, erasure included, and reading any setting through `ISettingProvider` fails as
undeclared. The example on `AddStrataraErasure` was exactly that composition. A host that declared an
empty catalog as a workaround can drop it.
- **A second `AddSettingCatalog` or `AddPermissionCatalog` call no longer replaces the first.** Each
call registered a new catalog and the last one won, so a host declaring its vocabulary per module kept
only the last module's — the others' settings failed as undeclared and their permissions were denied
without a word. Every call now adds to the one registered catalog, in whichever order it runs relative
to the setting store. A setting name declared in two parts throws as a duplicate; a redeclared
permission has no effect and grants to one role accumulate. A catalog registered as a factory cannot be
added to, and the next call now throws `InvalidOperationException` saying so instead of replacing it.