Swevo.EFCore.Outbox
1.0.1
Prefix Reserved
dotnet add package Swevo.EFCore.Outbox --version 1.0.1
NuGet\Install-Package Swevo.EFCore.Outbox -Version 1.0.1
<PackageReference Include="Swevo.EFCore.Outbox" Version="1.0.1" />
<PackageVersion Include="Swevo.EFCore.Outbox" Version="1.0.1" />
<PackageReference Include="Swevo.EFCore.Outbox" />
paket add Swevo.EFCore.Outbox --version 1.0.1
#r "nuget: Swevo.EFCore.Outbox, 1.0.1"
#:package Swevo.EFCore.Outbox@1.0.1
#addin nuget:?package=Swevo.EFCore.Outbox&version=1.0.1
#tool nuget:?package=Swevo.EFCore.Outbox&version=1.0.1
Swevo.EFCore.Outbox
Transactional outbox pattern for EF Core + MassTransit. Enqueue domain events inside your existing SaveChanges transaction and publish them reliably via a background processor — zero message loss even if the bus is temporarily unavailable.
How It Works
┌─────────────────────────────────────────────────┐
│ Your service │
│ │
│ 1. outbox.Add(new OrderPlaced(...)) │
│ 2. dbContext.SaveChangesAsync() │
│ │
│ ┌──────────────────┐ atomic ┌────────────┐ │
│ │ domain changes │──────────│ OutboxMsg │ │
│ └──────────────────┘ └────────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ OutboxProcessor (BackgroundService) │
│ │
│ 3. SELECT * FROM OutboxMessages WHERE │
│ ProcessedAt IS NULL ORDER BY CreatedAt │
│ 4. publisher.Publish(message) │
│ 5. UPDATE ProcessedAt = NOW() │
└─────────────────────────────────────────────────┘
Installation
dotnet add package Swevo.EFCore.Outbox
Requires EF Core 8+ and MassTransit 9+.
Quick Start
1. Configure your DbContext
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<OutboxMessage> OutboxMessages => Set<OutboxMessage>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.AddOutboxMessages(); // registers the OutboxMessage entity
}
}
2. Register services
// Program.cs
builder.Services.AddOutbox<AppDbContext>(options =>
{
options.PollingInterval = TimeSpan.FromSeconds(5); // default: 10s
options.BatchSize = 50; // default: 100
});
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
options.UseSqlServer(connectionString);
options.AddOutboxInterceptor(sp); // wires the scoped interceptor
});
3. Use in your services
public class OrderService(IOutbox outbox, AppDbContext db)
{
public async Task PlaceOrder(PlaceOrderCommand cmd)
{
db.Orders.Add(new Order { Id = cmd.OrderId, Total = cmd.Total });
// Enqueued atomically — written in the same SaveChanges transaction
outbox.Add(new OrderPlaced(cmd.OrderId, cmd.Total));
await db.SaveChangesAsync();
// ✓ Order row saved
// ✓ OutboxMessage row saved } in one DB transaction
// ✗ Bus not involved yet
}
}
4. Add EF migration
dotnet ef migrations add AddOutboxMessages
dotnet ef database update
Architecture
IOutbox (scoped)
Collects messages before SaveChanges. Injected into your service classes.
outbox.Add(new OrderPlaced(orderId, total)); // enqueue
outbox.Add(new PaymentCharged(paymentId, total)); // multiple per save cycle
OutboxInterceptor (scoped SaveChangesInterceptor)
Automatically runs during SaveChanges — no extra code needed after registration. Writes all pending IOutbox messages to the OutboxMessages table within the same database transaction.
OutboxProcessor<TContext> (BackgroundService)
Polls the OutboxMessages table, publishes via IPublishEndpoint, and marks messages as processed. Handles errors per-message — a single failing publish doesn't block the rest of the batch.
OutboxOptions
| Property | Default | Description |
|---|---|---|
PollingInterval |
10 seconds | How often to check for pending messages |
BatchSize |
100 | Max messages processed per poll cycle |
Integration with AutoAudit
Use alongside AutoAudit to get both audit fields and reliable messaging:
[Auditable]
public partial class Order { ... }
// In your service:
db.Orders.Add(order);
outbox.Add(new OrderPlaced(order.Id));
await db.SaveChangesAsync();
// CreatedAt/UpdatedAt set by AuditInterceptor
// OrderPlaced written to OutboxMessages by OutboxInterceptor
// Both in one transaction
Compatibility
| Dependency | Version |
|---|---|
| EF Core | 8.0+ |
| MassTransit | 9.x |
| .NET | net8.0+ |
License
MIT © 2025 Justin Bannister
| 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 was computed. 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 was computed. 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. |
-
net8.0
- MassTransit.Abstractions (>= 9.1.2)
- Microsoft.EntityFrameworkCore (>= 8.0.28)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.0.0: Initial release. Transactional outbox with EF Core SaveChangesInterceptor and MassTransit IPublishEndpoint background processor.