Skip to main content
Version: Next

Outbox EF Core Integration

The Hermodr.Publisher.Outbox.EntityFramework package eliminates most boilerplate by providing ready-made EF Core implementations.

What's Included

TypeRole
DbOutboxMessageComplete IOutboxMessage implementation with EF mappings
EntityOutboxMessageRepository<TMessage>EF Core repository implementation
OutboxDbContextMinimal DbContext with outbox configured
WithEntityFramework()Fluent extension that wires all EF components
AddEntityFrameworkOutbox()Convenience method combining outbox + EF registration

DbOutboxMessage

DbOutboxMessage is a complete, concrete implementation of IOutboxMessage — it can be used directly as your outbox entity without writing any additional code.

Features

  • Maps well-known CloudEvents context attributes as scalar columns
  • Stores data payload in DataText (string/JSON) or DataBytes (binary)
  • Extension attributes stored as child rows in CloudEventAttributes table
  • Full IOutboxMessage implementation provided

Column Mapping

ColumnTypePurpose
IdstringPrimary key (GUID without hyphens)
EventTypestringCloudEvents type attribute
SourcestringCloudEvents source attribute
Subjectstring?CloudEvents subject attribute
EventTimeDateTimeOffsetCloudEvents time attribute
DataContentTypestring?CloudEvents datacontenttype
DataSchemastring?CloudEvents dataschema
DataTextstring?JSON payload (for JSON content types)
DataBytesbyte[]?Binary payload (for non-JSON content types)
StatusOutboxMessageStatusDelivery lifecycle status
ErrorMessagestring?Last failure reason
RetryCountintNumber of delivery attempts
NextRetryAtDateTimeOffset?Scheduled retry time
CreatedAtDateTimeOffsetWhen the record was created
LastStatusAtDateTimeOffsetWhen status last changed

Using DbOutboxMessage Directly

When you don't need any application-specific columns, register DbOutboxMessage itself:

builder.Services
.AddEventPublisher()
.AddOutbox<DbOutboxMessage>() // no subclass needed
.WithEntityFramework(opts => opts.UseSqlServer(connectionString))
.WithFactory<DbOutboxMessageFactory>()
.WithRelay();

Subclassing for Application-Specific Columns

Derive from DbOutboxMessage only when you need to add columns:

using Hermodr.Publisher.Outbox.EntityFramework;

public class OrderOutboxMessage : DbOutboxMessage
{
public string? TenantId { get; set; }
public string? OrderId { get; set; } // For indexing/querying
}

Constraint: WithEntityFramework() requires that the message entity type derives from DbOutboxMessage. It checks this at registration time and throws InvalidOperationException if the constraint is not satisfied.


Message Factory with DbOutboxMessage

Use the PopulateFromCloudEvent helper inside your factory:

Factory for DbOutboxMessage (No Subclass)

using Hermodr.Publisher.Outbox.EntityFramework;

public class DbOutboxMessageFactory : IOutboxMessageFactory<DbOutboxMessage>
{
public DbOutboxMessage Create(CloudEvent cloudEvent, OutboxPublishOptions? options = null)
{
var message = new DbOutboxMessage();
message.PopulateFromCloudEvent(cloudEvent);
return message;
}
}

Factory for a Subclass with Extra Columns

public class OrderOutboxMessageFactory : IOutboxMessageFactory<OrderOutboxMessage>
{
public OrderOutboxMessage Create(CloudEvent cloudEvent, OutboxPublishOptions? options = null)
{
var message = new OrderOutboxMessage();
message.PopulateFromCloudEvent(cloudEvent); // populates all standard columns
message.TenantId = /* resolve from context */;
message.OrderId = cloudEvent["orderid"]?.ToString();
return message;
}
}

PopulateFromCloudEvent maps all standard attributes and extension attributes from the live CloudEvent to the entity columns and child rows.


OutboxDbContext

OutboxDbContext is a minimal DbContext that applies the built-in EF model configuration automatically:

using Hermodr.Publisher.Outbox.EntityFramework;
using Microsoft.EntityFrameworkCore;

public class OutboxDbContext : DbContext
{
public OutboxDbContext(DbContextOptions<OutboxDbContext> options) : base(options) { }

public DbSet<DbOutboxMessage> OutboxMessages { get; set; } = null!;
public DbSet<DbCloudEventAttribute> CloudEventAttributes { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

// Apply built-in configurations
modelBuilder.ApplyConfiguration(new DbOutboxMessageConfiguration());
modelBuilder.ApplyConfiguration(new DbCloudEventAttributeConfiguration());
}
}

Deriving from OutboxDbContext

When you need to add your own DbSet properties:

public class AppDbContext : OutboxDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

public DbSet<Order> Orders { get; set; } = null!;
}

Note: Pass the derived context's own DbContextOptions<AppDbContext> through the constructor chain to the protected OutboxDbContext(DbContextOptions) overload.


Registration

Minimal Setup (Using DbOutboxMessage Directly)

builder.Services.AddDbContext<OutboxDbContext>(opts =>
opts.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

builder.Services
.AddEventPublisher()
.AddOutbox<DbOutboxMessage>()
.WithEntityFramework()
.WithFactory<DbOutboxMessageFactory>()
.WithRelay();

With Subclass for Extra Columns

builder.Services.AddDbContext<AppDbContext>(opts =>
opts.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

builder.Services
.AddEventPublisher()
.AddOutbox<OrderOutboxMessage>() // OrderOutboxMessage : DbOutboxMessage
.WithEntityFramework()
.WithFactory<OrderOutboxMessageFactory>()
.WithRelay(relay =>
{
relay.Interval = TimeSpan.FromSeconds(15);
relay.MaxBatchSize = 100;
});

Convenience Method: AddEntityFrameworkOutbox

An AddEntityFrameworkOutbox method is provided for convenience, which combines the outbox and EF registration in one call:

builder.Services
.AddEventPublisher()
.AddEntityFrameworkOutbox(opts => opts.UseSqlServer(connectionString))
.WithRelay();

Note: When you call .WithEntityFramework(), the framework automatically registers the EntityOutboxMessageRepository<DbOutboxMessage> implementation for you, so you do not need to call .WithStore<…>() separately. It also registers the IOutboxMessageFactory implementation that constructs DbOutboxMessage instances.


Database Schema

When you run migrations or call Database.EnsureCreated(), the following tables are created:

OutboxMessages Table

CREATE TABLE OutboxMessages (
Id nvarchar(450) NOT NULL PRIMARY KEY,
EventType nvarchar(256) NOT NULL,
Source nvarchar(256) NOT NULL,
Subject nvarchar(256) NULL,
EventTime datetimeoffset NOT NULL,
DataContentType nvarchar(256) NULL,
DataSchema nvarchar(256) NULL,
DataText nvarchar(max) NULL,
DataBytes varbinary(max) NULL,
Status int NOT NULL DEFAULT 0,
ErrorMessage nvarchar(max) NULL,
RetryCount int NOT NULL DEFAULT 0,
NextRetryAt datetimeoffset NULL,
CreatedAt datetimeoffset NOT NULL,
LastStatusAt datetimeoffset NOT NULL
);

CloudEventAttributes Table

CREATE TABLE CloudEventAttributes (
Id nvarchar(450) NOT NULL PRIMARY KEY,
OutboxMessageId nvarchar(450) NOT NULL,
AttributeName nvarchar(256) NOT NULL,
AttributeValue nvarchar(max) NULL,
CONSTRAINT FK_CloudEventAttributes_OutboxMessages
FOREIGN KEY (OutboxMessageId) REFERENCES OutboxMessages(Id)
ON DELETE CASCADE
);