Reliable ERP integration in ASP.NET Core: transactional outbox pattern with EF Core

ASP.NET Core Interview Q&A November 13, 2026 csharp

Transactional Outbox: the order and the message for the ERP are written in the same transaction.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ======================================================================
// Q: Our shop must send orders to the ERP. The ERP is sometimes down. Design it.
//
// Transactional Outbox: the order and the message for the ERP are written in the
// same transaction. A background worker sends the message with retries.
// The worker reads unsent messages (PostgreSQL: FOR UPDATE SKIP LOCKED, so
// multiple instances don't pick up the same one), sends them and marks them as
// processed. After N failures: dead-letter, alert, manual replay. Alternatively,
// Azure Service Bus with its built-in DLQ.
// Key point: idempotency. With retries the ERP may receive the same message
// twice. Send an Idempotency-Key or the OrderNumber as an external reference so
// the ERP rejects duplicates.
// ======================================================================

// Save the order and the outbox message in one transaction
await using var tx = await _db.Database.BeginTransactionAsync(ct);
_db.Orders.Add(order);
_db.Outbox.Add(new OutboxMessage
{
    Id = Guid.NewGuid(),
    Type = "OrderCreated",
    Payload = JsonSerializer.Serialize(new { order.Id, order.OrderNumber }),
    CreatedAt = DateTime.UtcNow
});
await _db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);