Getting Started

Using TransactionalSession with Entity Framework and ASP.NET Core

NuGet Package: NServiceBus.Persistence.Sql.TransactionalSession (6.x)
Target Version: NServiceBus 7.x
NServiceBus integrates with ASP.NET Core 3.x using GenericHost. For older versions of ASP.NET Core use the community package Community.NServiceBus.WebHost. ASP.NET Core 2.x has a race condition and the community package implements a workaround. It's recommended to upgrade to ASP.NET Core 3.0 and use NServiceBus.Extensions.Hosting package.

This sample shows how to send messages and modify data in a database atomically within the scope of a web request using the NServiceBus.TransactionalSession package with ASP.NET Core. The operations are triggered by an incoming HTTP request to ASP.NET Core which will manage the ITransactionalSession lifetime inside a request middleware.


Ensure an instance of SQL Server (Version 2012 or above) is installed and accessible on localhost and port 1433.

Alternatively, change the connection string to point to different SQL Server instance.

At startup each endpoint will create the required SQL assets including databases, tables, and schemas.

Running the solution

When the solution is run, a new browser window/tab opens, as well as a console application. The browser will navigate to http://localhost:58118/.

An async WebAPI controller handles the request. It stores a new document using Entity Framework and sends an NServiceBus message to the endpoint hosted in the console application.

The message will be processed by the NServiceBus message handler and result in "Message received at endpoint"-message printed to the console. In addition, the handler will update the previously created entity.

For querying all the stored entities, navigate to http://localhost:58118/all.


The endpoint is configured using the UseNServiceBus extension method:

.UseNServiceBus(context =>
    var endpointConfiguration = new EndpointConfiguration("Samples.ASPNETCore.Sender");
    var transport = endpointConfiguration.UseTransport<LearningTransport>();

    var persistence = endpointConfiguration.UsePersistence<SqlPersistence>();
    persistence.ConnectionBuilder(() => new SqlConnection(ConnectionString));



    return endpointConfiguration;

The transactional session is enabled via the endpointConfiguration.EnableTransactionalSession() method call. Note that the transactional session feature requires the outbox to be configured to ensure that operations across the storage and the message broker are atomic.

ASP.NET Core uses ConfigureWebHostDefaults for configuration and a custom result filter is registered for the ITransactionalSession lifetime management:

s.AddControllers(o => o.Filters.AddService<MessageSessionFilter>());

Entity Framework support is configured by registering the DbContext:

.ConfigureServices(c =>
    // Configure Entity Framework to attach to the synchronized storage session when required
    c.AddScoped(b =>
        var synchronizedStorageSession = b.GetService<SynchronizedStorageSession>();
        if (synchronizedStorageSession != null)
            var session = synchronizedStorageSession.SqlPersistenceSession();
            var context = new MyDataContext(new DbContextOptionsBuilder<MyDataContext>()

            //Use the same underlying ADO.NET transaction

            //Ensure context is flushed before the transaction is committed
            session.OnSaveChanges((s) => context.SaveChangesAsync());

            return context;
            var context = new MyDataContext(new DbContextOptionsBuilder<MyDataContext>()
            return context;

The registration ensures that the MyDataContext type is built using the same session and transaction that is used by the ITransactionalSession. Once the transactional session is committed, it notifies the Entity Framework context to call SaveChangesAsync. For cases where the transactional session is not in play, a data context with a dedicated connection is returned.

Using the session

The message session is injected into SendMessageController via method injection. Message operations executed on the ITransactionalSession API are transactionally consistent with the database operations performed on the MyDataContext.

public async Task<string> Get([FromServices] ITransactionalSession messageSession)
    var id = Guid.NewGuid().ToString();

    await dataContext.MyEntities.AddAsync(new MyEntity { Id = id, Processed = false });

    var message = new MyMessage { EntityId = id };
    await messageSession.SendLocal(message)

    return $"Message with entity ID '{id}' sent to endpoint";

The lifecycle of the session is managed by the MessageSessionFilter which hooks into the result filter part of the ASP.NET pipeline. It opens the session when a controller action is called that takes a dependency to the ITransactionalSession interface, committing the session once the action completes:

public class MessageSessionFilter : IAsyncResourceFilter
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        if (context.ActionDescriptor.Parameters.Any(p => p.ParameterType == typeof(ITransactionalSession)))
            var session = context.HttpContext.RequestServices.GetRequiredService<ITransactionalSession>();
            await session.Open(new SqlPersistenceOpenSessionOptions());

            await next();

            await session.Commit();
            await next();
The result filter could be extended to return problem details (for example, with context.Result = new ObjectResult(new ProblemDetails())) in cases when the transactional session cannot be committed. This part has been left out in the sample.

For controller actions that do not take a dependency to ITransactionalSession a data context with a dedicated connection is used.

public async Task<List<MyEntity>> GetAll()
    return await dataContext.MyEntities.ToListAsync();
The sample uses method injection as an opinionated way of expressing the need for having the transactional boundaries managed by the infrastructure. For cases when constructor injection is preferred, it would be required to introduce an action attribute and annotate the controllers or the actions accordingly.

This diagram visualizes the interaction between the result filter, ITransactionalSession, and the Web API controller:

sequenceDiagram autonumber User->>Filter: Http Request activate Filter Filter->>TransactionalSession: Open activate TransactionalSession TransactionalSession-->>Filter: Reply Filter->>Controller: next() activate Controller Controller->>TransactionalSession: Send/Publish... Controller->>TransactionalSession: Use SynchronizedStorageSession deactivate Controller Filter->>TransactionalSession: Commit deactivate TransactionalSession Filter-->>User: Reply deactivate Filter

Handling the message

The MyHandler handles the message sent by the ASP.NET controller and accesses the previously committed data stored by the controller:

public class MyHandler : IHandleMessages<MyMessage>
    static readonly ILog log = LogManager.GetLogger<MyHandler>();
    readonly MyDataContext dataContext;

    public MyHandler(MyDataContext dataContext)
        this.dataContext = dataContext;

    public async Task Handle(MyMessage message, IMessageHandlerContext context)
        log.Info("Message received at endpoint");

        var entity = await dataContext.MyEntities.Where(e => e.Id == message.EntityId)
        entity.Processed = true;

Related Articles

Last modified