ZeroAlloc.Outbox.EfCore
4.4.5
dotnet add package ZeroAlloc.Outbox.EfCore --version 4.4.5
NuGet\Install-Package ZeroAlloc.Outbox.EfCore -Version 4.4.5
<PackageReference Include="ZeroAlloc.Outbox.EfCore" Version="4.4.5" />
<PackageVersion Include="ZeroAlloc.Outbox.EfCore" Version="4.4.5" />
<PackageReference Include="ZeroAlloc.Outbox.EfCore" />
paket add ZeroAlloc.Outbox.EfCore --version 4.4.5
#r "nuget: ZeroAlloc.Outbox.EfCore, 4.4.5"
#:package ZeroAlloc.Outbox.EfCore@4.4.5
#addin nuget:?package=ZeroAlloc.Outbox.EfCore&version=4.4.5
#tool nuget:?package=ZeroAlloc.Outbox.EfCore&version=4.4.5
ZeroAlloc.Outbox
Source-generated transactional outbox for .NET: mark a message with [OutboxMessage] and get a typed writer, a polling worker with retry and dead-lettering, and NativeAOT support, within a few percent of a hand-written outbox.
Why ZeroAlloc.Outbox
- Within 2–9% of a hand-rolled outbox. Against a correctness-matched SQLite outbox on the same connection, an enqueue is +2% time and a 10-message dispatch tick +9%, with 7% less allocated.
- Allocates only the payload. An enqueue allocates just the exact-size payload array, 72 B for a small struct message. The worker loop adds nothing per message beyond deserialising it, and a MemoryPack struct deserialises with 0 B. All three are asserted by tests.
- Works under NativeAOT and trimming. Writers and dispatchers are generated at compile time, with no
Type.GetTypeorMakeGenericType. CI publishes and runs an AOT app with IL warnings as errors. - Compile-time diagnostics. ZO0001 to ZO0007 flag message types the generator cannot handle, such as interfaces, nested types or colliding names, at build time.
Install
dotnet add package ZeroAlloc.Outbox
dotnet add package ZeroAlloc.Outbox.InMemory
The source generator ships inside ZeroAlloc.Outbox. The quick start uses the in-memory store; production apps use the ORM or EF Core store below.
Quick start
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ZeroAlloc.Outbox;
using ZeroAlloc.Outbox.InMemory;
using ZeroAlloc.Serialisation;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSerializerDispatcher(); // generated, AOT-safe
builder.Services.AddOutbox(o => o.PollingInterval = TimeSpan.FromMilliseconds(100))
.WithInMemoryStore() // or .WithOrm() / .WithEfCore<AppDbContext>() in production
.AddOrderPlacedOutbox(); // generated for the [OutboxMessage] type
builder.Services.AddSingleton<IOutboxDispatcher<OrderPlaced>, SendOrderPlaced>();
using var host = builder.Build();
await host.StartAsync();
await using (var scope = host.Services.CreateAsyncScope())
{
var outbox = scope.ServiceProvider.GetRequiredService<IOutboxWriter<OrderPlaced>>();
await outbox.WriteAsync(new OrderPlaced(42, 9.95m)); // pass your DbTransaction for an atomic write
}
await Task.Delay(500); // the worker claims the message and dispatches it
await host.StopAsync();
[OutboxMessage, ZeroAllocSerializable(SerializationFormat.SystemTextJson)]
public sealed record OrderPlaced(int OrderId, decimal Amount);
[JsonSerializable(typeof(OrderPlaced))]
internal sealed partial class AppJson : JsonSerializerContext;
public sealed class SendOrderPlaced : IOutboxDispatcher<OrderPlaced>
{
public ValueTask DispatchAsync(OrderPlaced message, CancellationToken ct)
{
Console.WriteLine($"Dispatched order {message.OrderId}"); // publish to your bus here
return ValueTask.CompletedTask;
}
}
ZeroAllocSerializable and the generated AddSerializerDispatcher() come from ZeroAlloc.Serialisation, which ZeroAlloc.Outbox brings in. If you don't need AOT, .WithSystemTextJsonSerializer() is a one-line reflection-based alternative. The getting started guide walks through the EF Core setup.
What you get
- A typed
IOutboxWriter<T>per message type that writes on your own database transaction. - Typed dispatchers: implement
IOutboxDispatcher<T>once and send to any bus, HTTP endpoint or mailbox. - A background worker with lease-based claiming, so several hosts never dispatch the same row twice.
- Exponential-backoff retry and dead-lettering with the last failure reason.
- Named pipelines, each with its own store, options and worker.
Stores and integrations:
ZeroAlloc.Outbox.Orm: raw SQL through ZeroAlloc.ORM on SQLite, PostgreSQL or SQL Server, NativeAOT on net10.0.ZeroAlloc.Outbox.EfCore: store on your existingDbContext.ZeroAlloc.Outbox.InMemory: in-process store for unit and integration tests.ZeroAlloc.Outbox.Mediator: dispatch through ZeroAlloc.MediatorIMediator.Publish.ZeroAlloc.Outbox.Resilience: wrap dispatchers in a ZeroAlloc.Resilience policy.ZeroAlloc.Outbox.Telemetry: OpenTelemetry spans and metrics around dispatch.ZeroAlloc.Outbox.Dashboard: live web dashboard to inspect, requeue, cancel or force-dispatch messages. Always put it behind authorization.ZeroAlloc.Outbox.Dashboard.Blazor: the dashboard as a Blazor component.

Performance
Against a hand-rolled SQLite outbox on the same connection, both transactional. .NET 10.0.7, i9-12900HK, BenchmarkDotNet v0.15.4.
| Operation | Hand-rolled | ZA.Outbox | Overhead |
|---|---|---|---|
| Enqueue (1 message, transactional) | 6.86 µs / 2.08 KB | 6.99 µs / 2.13 KB | +2% time, +2% alloc |
| Dispatch tick (10 messages) | 105.4 µs / 11.9 KB | 115.0 µs / 11.09 KB | +9% time, −7% alloc |
Full numbers, including enqueue serialisation and dispatch deserialisation budgets: docs/performance.md.
Learn more
- Documentation: outbox.zeroalloc.net
- Feature reference: docs/index.md, diagnostics: docs/diagnostics.md
- Upgrading: migrating to v3, migrating to v4
- Related: ZeroAlloc.Saga, ZeroAlloc.EventSourcing, ZeroAlloc.Mediator, ZeroAlloc.Serialisation
- Contributing: issues and pull requests are welcome on GitHub
- License: MIT, see LICENSE
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- Microsoft.EntityFrameworkCore (>= 10.0.12)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.12)
- ZeroAlloc.Outbox (>= 4.4.5)
- ZeroAlloc.ValueObjects.EfCore (>= 2.2.2)
-
net8.0
- Microsoft.EntityFrameworkCore (>= 9.0.20)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.20)
- ZeroAlloc.Outbox (>= 4.4.5)
- ZeroAlloc.ValueObjects.EfCore (>= 2.2.2)
-
net9.0
- Microsoft.EntityFrameworkCore (>= 9.0.20)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.20)
- ZeroAlloc.Outbox (>= 4.4.5)
- ZeroAlloc.ValueObjects.EfCore (>= 2.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.4.5 | 0 | 10/16/2026 |
| 4.4.4 | 177 | 10/15/2026 |
| 4.4.3 | 397 | 10/12/2026 |
| 4.4.2 | 232 | 10/11/2026 |
| 4.4.1 | 243 | 10/11/2026 |
| 4.4.0 | 289 | 10/10/2026 |
| 4.3.0 | 166 | 10/10/2026 |
| 4.2.0 | 361 | 9/30/2026 |
| 4.1.0 | 256 | 9/30/2026 |
| 4.0.1 | 220 | 9/28/2026 |
| 4.0.0 | 135 | 9/27/2026 |
| 3.0.1 | 714 | 9/26/2026 |
| 3.0.0 | 114 | 9/26/2026 |
| 2.7.2 | 189 | 9/25/2026 |
| 2.7.1 | 314 | 9/22/2026 |
| 2.7.0 | 87 | 9/22/2026 |
| 2.6.0 | 118 | 9/20/2026 |
| 2.5.5 | 113 | 9/20/2026 |
| 2.5.4 | 344 | 9/20/2026 |
| 2.5.3 | 101 | 9/19/2026 |