Skip to content

The search box in the website knows all the secrets—try it!

For any queries, join our Discord Channel to reach us faster.

JasperFx Logo

JasperFx provides formal support for Wolverine and other JasperFx libraries. Please check our Support Plans for more details.

Transactional Middleware

Support for using Wolverine transactional middleware requires an explicit registration on WolverineOptions shown below (it's an extension method):

cs
builder.Host.UseWolverine(opts =>
{
    // Setting up Sql Server-backed message storage
    // This requires a reference to Wolverine.SqlServer
    opts.PersistMessagesWithSqlServer(connectionString, "wolverine");

    // Set up Entity Framework Core as the support
    // for Wolverine's transactional middleware
    opts.UseEntityFrameworkCoreTransactions();

    // Enrolling all local queues into the
    // durable inbox/outbox processing
    opts.Policies.UseDurableLocalQueues();
});

snippet source | anchor

TIP

When using the opt in Handlers.AutoApplyTransactions() option, Wolverine (really Lamar) can detect that your handler method uses a DbContext if it's a method argument, a dependency of any service injected as a method argument, or a dependency of any service injected as a constructor argument of the handler class.

That will enroll EF Core as both a strategy for stateful saga support and for transactional middleware. With this option added, Wolverine will wrap transactional middleware around any message handler that has a dependency on any type of DbContext like this one:

cs
[Transactional]
public static ItemCreated Handle(
    // This would be the message
    CreateItemCommand command,

    // Any other arguments are assumed
    // to be service dependencies
    ItemsDbContext db)
{
    // Create a new Item entity
    var item = new Item
    {
        Name = command.Name
    };

    // Add the item to the current
    // DbContext unit of work
    db.Items.Add(item);

    // This event being returned
    // by the handler will be automatically sent
    // out as a "cascading" message
    return new ItemCreated
    {
        Id = item.Id
    };
}

snippet source | anchor

When using the transactional middleware around a message handler, the DbContext is used to persist the outgoing messages as part of Wolverine's outbox support.

Opting Out with [NonTransactional]

When using AutoApplyTransactions(), you can opt specific handlers or HTTP endpoints out of transactional middleware by decorating them with the [NonTransactional] attribute:

cs
using Wolverine.Attributes;

public static class MyHandler
{
    // This handler will NOT have transactional middleware applied
    // even when AutoApplyTransactions() is enabled
    [NonTransactional]
    public static void Handle(MyCommand command, MyDbContext db)
    {
        // You're managing the DbContext yourself here
    }
}

The [NonTransactional] attribute can be placed on individual handler methods or on the handler class to opt out all methods in that class.

Eager vs Lightweight Transactions 5.15

By default, the EF Core middleware will run in Eager mode meaning that Wolverine will call DbContext.Database.BeginTransactionAsync() before your message handler or HTTP endpoint handler. We do this so that bulk operations can succeed. If all you need to do is persist entities such that DbContext.SaveChangesAsync() gives you all the transactional integrity you need, you can opt into lightweight transaction code generation instead:

cs
using var host = await Host.CreateDefaultBuilder()
    .UseWolverine(opts =>
    {
        opts.Durability.Mode = DurabilityMode.Solo;

        opts.Services.AddDbContextWithWolverineIntegration<CleanDbContext>(x =>
            x.UseSqlServer(Servers.SqlServerConnectionString));

        opts.PersistMessagesWithSqlServer(Servers.SqlServerConnectionString, "txmode");
        
        // ONLY use SaveChangesAsync() for transaction boundaries
        // Treat the DbContext as a unit of work, assume there are no
        // bulk operations
        opts.UseEntityFrameworkCoreTransactions(TransactionMiddlewareMode.Lightweight);
        opts.Policies.AutoApplyTransactions();

        opts.Discovery.DisableConventionalDiscovery()
            .IncludeType<LightweightModeHandler>();
    }).StartAsync();

snippet source | anchor

You can also selectively configure the transaction middleware mode on singular message handlers or HTTP endpoints with the [Transactional] attribute like this:

cs
public class LightweightAttributeHandler
{
    [Transactional(Mode = TransactionMiddlewareMode.Lightweight)]
    public static void Handle(LightweightAttributeMessage message, CleanDbContext db)
    {
    }
}

snippet source | anchor

Auto Apply Transactional Middleware

You can opt into automatically applying the transactional middleware to any handler that depends on a DbContext type with the AutoApplyTransactions() option as shown below:

cs
var builder = Host.CreateApplicationBuilder();
builder.UseWolverine(opts =>
{
    var connectionString = builder.Configuration.GetConnectionString("database");

    opts.Services.AddDbContextWithWolverineIntegration<SampleDbContext>(x =>
    {
        x.UseSqlServer(connectionString);
    });

    // Add the auto transaction middleware attachment policy
    opts.Policies.AutoApplyTransactions();
});

using var host = builder.Build();
await host.StartAsync();

snippet source | anchor

With this option, you will no longer need to decorate handler methods with the [Transactional] attribute.

Transaction Middleware Mode

By default, the EF Core transactional middleware uses TransactionMiddlewareMode.Eager, which eagerly opens an explicit database transaction via Database.BeginTransactionAsync() before the handler executes. This is appropriate when you need explicit transaction control, such as when using EF Core bulk operations.

If you prefer to rely solely on DbContext.SaveChangesAsync() as your transactional boundary without opening an explicit database transaction, you can use TransactionMiddlewareMode.Lightweight:

cs
builder.Host.UseWolverine(opts =>
{
    opts.PersistMessagesWithSqlServer(connectionString, "wolverine");

    // Use Lightweight mode — no explicit transaction, relies on SaveChangesAsync()
    opts.UseEntityFrameworkCoreTransactions(TransactionMiddlewareMode.Lightweight);

    opts.Policies.UseDurableLocalQueues();
});

TIP

TransactionMiddlewareMode.Lightweight is not supported or necessary for Marten or RavenDb, which have their own unit of work implementations.

Per-Handler Override

You can override the global TransactionMiddlewareMode for individual handlers using the [Transactional] attribute's Mode property:

cs
// This handler will use an explicit transaction even if the global mode is Lightweight
[Transactional(Mode = TransactionMiddlewareMode.Eager)]
public static ItemCreated Handle(CreateItemCommand command, ItemsDbContext db)
{
    var item = new Item { Name = command.Name };
    db.Items.Add(item);
    return new ItemCreated { Id = item.Id };
}

// This handler skips the explicit transaction even if the global mode is Eager
[Transactional(Mode = TransactionMiddlewareMode.Lightweight)]
public static void Handle(UpdateItemCommand command, ItemsDbContext db)
{
    // Just uses SaveChangesAsync() without an explicit transaction
}

Released under the MIT License.