Getting Started
Architecture
NServiceBus
Transports
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

SqlSaga base class

Component: Sql Persistence
Target Version: NServiceBus 8.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, and to switch to SqlSaga<T> when advantageous to cut down on the need for repetitive .ToSaga(...) expressions in sagas that handle several message types.

SqlSaga definition

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

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

    protected override void ConfigureMapping(IMessagePropertyMapper mapper)
    {
        mapper.ConfigureMapping<StartOrder>(message => message.OrderId);
    }

    protected override string CorrelationPropertyName => nameof(SagaData.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 differences to how a standard NServiceBus saga is implemented.

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.

public class SagaWithCorrelation :
    SqlSaga<SagaWithCorrelation.SagaData>,
    IAmStartedByMessages<StartSagaMessage>
{
    protected override void ConfigureMapping(IMessagePropertyMapper mapper)
    {
        mapper.ConfigureMapping<StartSagaMessage>(message => message.CorrelationProperty);
    }

    protected override string CorrelationPropertyName => nameof(SagaData.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 IDs that co-exist. See also the Transitioning Correlation IDs sample.

public class SagaWithCorrelationAndTransitional :
    SqlSaga<SagaWithCorrelationAndTransitional.SagaData>,
    IAmStartedByMessages<StartSagaMessage>
{
    protected override void ConfigureMapping(IMessagePropertyMapper mapper)
    {
        mapper.ConfigureMapping<StartSagaMessage>(message => message.CorrelationProperty);
    }

    protected override string CorrelationPropertyName => nameof(SagaData.CorrelationProperty);

    protected override string TransitionalCorrelationPropertyName => nameof(SagaData.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 interrogates the JSON-serialized data stored in the database.

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

    protected override string CorrelationPropertyName => null;

    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:

class MySaga:SqlSaga<MySaga.SagaData>
{
    protected override string TableSuffix => "TheCustomTableName";

Related Articles