[](https://www.nuget.org/packages/Swevo.EFCore.Outbox/)
Transactional outbox pattern for EF Core + AutoBus. 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.
v2.0.0 breaking change: MassTransit's
IPublishEndpointhas been replaced with Swevo.AutoBus'sIMessageBus— a free, MIT-licensed alternative now that MassTransit v9 is commercial-only. See Migrating from MassTransit below.
┌─────────────────────────────────────────────────┐
│ 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. bus.PublishAsync(message) │
│ 5. UPDATE ProcessedAt = NOW() │
└─────────────────────────────────────────────────┘
dotnet add package Swevo.EFCore.OutboxRequires EF Core 8+ and Swevo.AutoBus 1.x. AutoBus is added automatically as a transitive dependency, but you must still call services.AddAutoBus(...) (with or without a transport, e.g. AddAutoBusRabbitMq) so IMessageBus is available in DI — see AutoBus's README for configuration.
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
}
}// 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
});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
}
}dotnet ef migrations add AddOutboxMessages
dotnet ef database updateCollects messages before SaveChanges. Injected into your service classes.
outbox.Add(new OrderPlaced(orderId, total)); // enqueue
outbox.Add(new PaymentCharged(paymentId, total)); // multiple per save cycleAutomatically runs during SaveChanges — no extra code needed after registration. Writes all pending IOutbox messages to the OutboxMessages table within the same database transaction.
Polls the OutboxMessages table, publishes via IMessageBus, and marks messages as processed. Handles errors per-message — a single failing publish doesn't block the rest of the batch.
| Property | Default | Description |
|---|---|---|
PollingInterval |
10 seconds | How often to check for pending messages |
BatchSize |
100 | Max messages processed per poll cycle |
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| Dependency | Version |
|---|---|
| EF Core | 8.0+ |
| Swevo.AutoBus | 1.x |
| .NET | net8.0+ |
OutboxProcessor<TContext> now resolves AutoBus.IMessageBus from DI instead of MassTransit's IPublishEndpoint. The call site is a drop-in replacement — both expose Publish(object message, Type messageType, CancellationToken) with the same semantics — so the migration is:
- Remove your MassTransit bus registration (or keep it running side-by-side if you still need it for consumers elsewhere).
- Add Swevo.AutoBus:
dotnet add package Swevo.AutoBus(addSwevo.AutoBus.RabbitMQtoo if you need a real transport instead of in-memory). - Register it:
builder.Services.AddAutoBus(cfg => { /* register any AutoBus consumers */ }); - Upgrade
Swevo.EFCore.Outboxto2.0.0. No changes required toIOutbox,OutboxInterceptor, or your domain event types.
If you still need to publish onto an existing MassTransit-based system, keep MassTransit registered in your app and write a small IConsumer<T> in AutoBus that forwards to MassTransit's IPublishEndpoint — this keeps EFCore.Outbox's own dependency footprint free of MassTransit while still supporting a gradual migration.
🌐 Full suite overview: swevo.github.io
| Package | Description |
|---|---|
| AutoLog.Generator | Compile-time high-performance logging — [Log(Level, Message)] generates LoggerMessage.Define. AOT-safe. |
| AutoHttpClient.Generator | Compile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative. |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No MediatR, no reflection. |
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code. |
| AutoMap.Generator | Compile-time object mapping with generated extension methods. AOT-safe AutoMapper alternative. |
| Package | Downloads | Description |
|---|---|---|
| Swevo.EFCore.StronglyTyped | Compile-time strongly-typed ID generation for | |
| Swevo.EFCore.SoftDelete | Compile-time soft-delete generation for EF Core entities using Roslyn source generators | |
| Swevo.EFCore.Seeding | Fluent, idempotent, dependency-ordered seed data for EF Core | |
| Swevo.EFCore.Pagination | Offset and cursor-based pagination for EF Core | |
| Swevo.EFCore.JsonColumn | Compile-time JSON column configuration for EF Core 8+ — [JsonColumn] on owned navigation properties generates ConfigureJsonColumns(ModelBuilder) with OwnsOne( | |
| Swevo.EFCore.BulkOperations | Free, MIT-licensed bulk insert/update/delete for EF Core | |
| Swevo.EFCore.MultiTenant | Compile-time multi-tenancy for EF Core | |
| Swevo.EFCore.RowVersion | Compile-time optimistic concurrency for EF Core — [Optimistic] source generator adds RowVersion property, IOptimisticEntity, and SaveChangesClientWinsAsync / SaveChangesDatabaseWinsAsync retry extensions |
MIT © 2025 Justin Bannister