TurboMediator
Persistence

Transactions

Automatic transaction management for handlers

The transaction behavior wraps handler execution in a database transaction, with automatic commit on success and rollback on failure.

Installation

dotnet add package TurboMediator.Persistence
dotnet add package TurboMediator.Persistence.EntityFramework  # For EF Core

Using the Attribute

[Transactional]
public record CreateOrderCommand(string Product, int Qty) : ICommand<Order>;

[Transactional(
    IsolationLevel = IsolationLevel.Serializable,
    TimeoutSeconds = 60,
    AutoSaveChanges = true
)]
public record TransferFundsCommand(
    Guid FromAccount,
    Guid ToAccount,
    decimal Amount
) : ICommand<TransferResult>;

Attribute Properties

PropertyDefaultDescription
IsolationLevelReadCommittedTransaction isolation level
TimeoutSeconds30Transaction timeout
AutoSaveChangestrueAuto-call SaveChanges on commit

ITransactionManager

public interface ITransactionManager
{
    bool HasActiveTransaction { get; }
    ValueTask<ITransactionScope> BeginTransactionAsync(
        IsolationLevel isolationLevel, CancellationToken ct);
    ValueTask<TResult> ExecuteWithStrategyAsync<TResult>(
        Func<CancellationToken, ValueTask<TResult>> operation, CancellationToken ct);
    ValueTask SaveChangesAsync(CancellationToken ct);
}

Configuration

builder.Services.AddTurboMediator(m =>
{
    // For specific message types
    m.WithTransaction<IBaseCommand, object>(options =>
    {
        options.IsolationLevel = IsolationLevel.ReadCommitted;
        options.AutoSaveChanges = true;
        options.UseExecutionStrategy = true;
        options.ThrowOnNestedTransaction = false;
    });

    // Or for all commands at once
    m.WithTransactionForCommands();
});

TransactionOptions

OptionDefaultDescription
IsolationLevelReadCommittedDefault isolation level
AutoSaveChangestrueAuto-save on commit
UseExecutionStrategytrueUse EF Core execution strategy (for retries)
ThrowOnNestedTransactionfalseThrow if a transaction is already active

Practical Example

[Transactional]
public record PlaceOrderCommand(
    Guid CustomerId,
    IReadOnlyList<OrderItem> Items
) : ICommand<Order>;

public class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand, Order>
{
    private readonly AppDbContext _db;

    public PlaceOrderHandler(AppDbContext db) => _db = db;

    public async ValueTask<Order> Handle(PlaceOrderCommand command, CancellationToken ct)
    {
        var order = new Order
        {
            CustomerId = command.CustomerId,
            Items = command.Items.Select(i => new OrderLineItem
            {
                ProductId = i.ProductId,
                Quantity = i.Quantity,
                Price = i.Price
            }).ToList()
        };

        _db.Orders.Add(order);
        // SaveChanges is called automatically by the transaction behavior
        // If any exception occurs, the transaction is rolled back

        return order;
    }
}

The transaction behavior detects nested transactions and reuses the existing transaction to avoid issues. Set ThrowOnNestedTransaction = true if you want strict transaction boundaries.

On this page