SqlSaga base class

Component: Sql Persistence
NuGet Package NServiceBus.Persistence.Sql (1.x)
Target NServiceBus Version: 6.x

SqlSaga<T> is an alternate saga base class for use with SQL Persistence that offers a less verbose mapping API than NServiceBus.Saga<T>. It's generally advisable to use NServiceBus.Saga<T> by default for most new projects, switching to SqlSaga<T> when advantageous to cut down on the need for repetitive .ToSaga(...) expressions in sagas that handle several message types.

In order to use the NServiceBus Core Saga<T> base class for sagas, use SQL Persistence Version 3.2 or above.

SqlSaga Definition

A saga can be implemented using the SqlSaga base class as follows:

[SqlSaga(
     correlationProperty: nameof(SagaData.OrderId)
 )]
public class OrderSaga :
    SqlSaga<OrderSaga.SagaData>,
    IAmStartedByMessages<StartOrder>
{
    static ILog log = LogManager.GetLogger<OrderSaga>();

    protected override void ConfigureMapping(MessagePropertyMapper<SagaData> mapper)
    {
        mapper.MapMessage<StartOrder>(_ => _.OrderId);
    }

    public Task Handle(StartOrder message, IMessageHandlerContext context)
    {
        log.Info($"Received StartOrder message {Data.OrderId}.");
        MarkAsComplete();
        return Task.CompletedTask;
    }

    public class SagaData :
        ContainSagaData
    {
        public Guid OrderId { get; set; }
    }
}

Note that there are some differences to how a standard NServiceBus saga is implemented.

SqlSagaAttribute

Sagas need to be decorated with a [SqlSagaAttribute]. If no Saga Finder is defined then the correlationProperty needs to match the Correlated Saga Property.

Correlation Ids

Instead of inferring the correlation property from multiple calls to .ToSaga(sagaData => sagaData.CorrelationPropertyName), the SqlSaga base class identifies the correlation property once as a class-level property.

Single Correlation Id

In most cases there will be a single correlation Id per Saga Type.

[SqlSaga(
    correlationProperty: nameof(SagaData.CorrelationProperty)
)]
public class SagaWithCorrelation :
    SqlSaga<SagaWithCorrelation.SagaData>,
    IAmStartedByMessages<StartSagaMessage>
{
    protected override void ConfigureMapping(MessagePropertyMapper<SagaData> mapper)
    {
        mapper.MapMessage<StartSagaMessage>(_ => _.CorrelationProperty);
    }

    public Task Handle(StartSagaMessage message, IMessageHandlerContext context)
    {
        return Task.CompletedTask;
    }

    public class SagaData :
        ContainSagaData
    {
        public Guid CorrelationProperty { get; set; }
    }
}

Correlation and Transitional Ids

During the migration from one correlation id to another correlation id there may be two correlation is that coexist. See also Transitioning Correlation ids Sample.

[SqlSaga(
    correlationProperty: nameof(SagaData.CorrelationProperty),
    transitionalCorrelationProperty: nameof(SagaData.TransitionalCorrelationProperty)
)]
public class SagaWithCorrelationAndTransitional :
    SqlSaga<SagaWithCorrelationAndTransitional.SagaData>,
    IAmStartedByMessages<StartSagaMessage>
{
    protected override void ConfigureMapping(MessagePropertyMapper<SagaData> mapper)
    {
        mapper.MapMessage<StartSagaMessage>(_ => _.CorrelationProperty);
        mapper.MapMessage<StartSagaMessage>(_ => _.TransitionalCorrelationProperty);
    }

    public Task Handle(StartSagaMessage message, IMessageHandlerContext context)
    {
        return Task.CompletedTask;
    }

    public class SagaData :
        ContainSagaData
    {
        public Guid CorrelationProperty { get; set; }
        public Guid TransitionalCorrelationProperty { get; set; }
    }
}

No Correlation Id

When implementing a Custom Saga Finder it is possible to have a message that does not map to a correlation id and instead interrogate the JSON-serialized data stored in the database.

[SqlSaga]
public class SagaWithNoMessageMapping :
    SqlSaga<SagaWithNoMessageMapping.SagaData>
{
    protected override void ConfigureMapping(MessagePropertyMapper<SagaData> mapper)
    {
    }

    public class SagaData :
        ContainSagaData
    {
    }
}

Table Suffix

A saga's table suffix, which forms part of the table name in the database, can also be defined more easily using a property override when using the SqlSaga<T> base class:

[SqlSaga(
    correlationProperty: "CorrelationProperty",
    tableSuffix: "TheCustomTableName"
)]

Related Articles


Last modified