0

We are using Masstransit with automatonymous and InMemoryRepository for saga persistence. We have around 3 state machines configured and working perfectly. We recently changed from InMemoryRepository to EFCore for persistence. This resulted in only the 1st configured state machine to work perfectly. Rest all statemachines are not even entering the Initially event. Need help to understand if the implementation is correct or not. Below are code details:

masstransit statemachine setup

 services.AddMassTransit(x =>
        {
            x.AddBus(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.Host(hostUri, hst =>
                {
                    hst.Username(appSettings.RabbitMQ.Username);
                    hst.Password(appSettings.RabbitMQ.Password);
                });

                cfg.ReceiveEndpoint("microservice-response", e =>
                {
                    AddConsumers(e, provider);
                    e.ConfigureSaga<ServiceRequest1RegisterState>(provider);
                    e.ConfigureSaga<ServiceRequest1UpdateState>(provider);
                    e.ConfigureSaga<ServiceRequest1ApproveState>(provider);
                });
            }));

            ////x.AddSagaStateMachine<ServiceRequest1RegisterStateMachine, ServiceRequest1RegisterState>()
            ////   .InMemoryRepository();
            ////x.AddSagaStateMachine<ServiceRequest1UpdateStateMachine, ServiceRequest1UpdateState>()
            ////   .InMemoryRepository();
            ////x.AddSagaStateMachine<ServiceRequest1ApproveStateMachine, ServiceRequest1ApproveState>()
            ////   .InMemoryRepository();

            x.AddSagaStateMachine<ServiceRequest1RegisterStateMachine, ServiceRequest1RegisterState>()
                .EntityFrameworkRepository(r =>
                {
                    r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                    r.AddDbContext<DbContext, ServiceRequest1RegisterStateDbContext>((provider, builder) =>
                    {
                        builder.UseSqlServer(configuration.GetConnectionString("StateDBConnection"), m =>
                        {
                            m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
                            m.MigrationsHistoryTable($"__{nameof(ServiceRequest1RegisterStateDbContext)}");
                        });
                    });
                });

    x.AddSagaStateMachine<ServiceRequest1UpdateStateMachine, ServiceRequest1UpdateState>()
                .EntityFrameworkRepository(r =>
                {
                    r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                    r.AddDbContext<DbContext, ServiceRequest1UpdateStateDbContext>((provider, builder) =>
                    {
                        builder.UseSqlServer(configuration.GetConnectionString("StateDBConnection"), m =>
                        {
                            m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
                            m.MigrationsHistoryTable($"__{nameof(ServiceRequest1UpdateStateDbContext)}");
                        });
                    });
                });

            x.AddSagaStateMachine<ServiceRequest1ApproveStateMachine, ServiceRequest1ApproveState>()
                .EntityFrameworkRepository(r =>
                {
                    r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                    r.AddDbContext<DbContext, ServiceRequest1ApproveStateDbContext>((provider, builder) =>
                    {
                        builder.UseSqlServer(configuration.GetConnectionString("StateDBConnection"), m =>
                        {
                            m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
                            m.MigrationsHistoryTable($"__{nameof(ServiceRequest1ApproveStateDbContext)}");
                        });
                    });
                });
        });

        services.AddSingleton<IHostedService, MassTransitBusService>();

DBContext looks like below for all 3 statemachines

 public class ServiceRequest1ApproveStateDbContext : SagaDbContext
{
    public ServiceRequest1ApproveStateDbContext(DbContextOptions<ServiceRequest1ApproveStateDbContext> options)
        : base(options)
    {
    }

    /// <summary>
    /// Gets the configurations.
    /// </summary>
    protected override IEnumerable<ISagaClassMap> Configurations
    {
        get { yield return new ServiceRequest1ApproveStateMap(); }
    }
}

State map below

public class ServiceRequest1ApproveStateMap : SagaClassMap<ServiceRequest1ApproveState>
{
    protected override void Configure(EntityTypeBuilder<ServiceRequest1ApproveState> entity, ModelBuilder model)
    {
        entity.Property(x => x.CurrentState).HasMaxLength(64);
        entity.Property(x => x.Id);
        entity.Property(x => x.ServiceId);
        entity.Property(x => x.ReadyEventStatus);
    }
}

From the above code, only the ServiceRequest1RegisterStateMachine works perfectly, the rest statemachines dont even enter Initially. I can confirm that the migrations are all done beforehand and these statemachine are all working fine individually when only 1 repository configuration exists, ie, if only repo config for ServiceRequest1ApproveStateMachine, this state machine works fine. But if all 3 repo config exists, then only the 1st works. I need guidance on correctly implementing saga persistence using EFCore. Should I try implementing using single dbcontext as mentioned in masstransit docs(https://masstransit-project.com/usage/sagas/efcore.html)

Rahul Jacob
  • 19
  • 1
  • 5
  • Yes, I would try using a single DbContext as shown in that updated section (available in 7.0.6). Though without knowing the actual error, I can't really suggest anything else. Were the migrations for the other two DbContext's applied to the database? – Chris Patterson Oct 27 '20 at 13:23
  • Yes, migrations for other 2 dbcontexts were applied first. Will try with the single dbcontext implementation and see if it resolve the issue. But just wanted to know, is the above implementation valid, as in, can we have multiple statemachines implemented as above? Is there a way to debug the orchestration? – Rahul Jacob Oct 27 '20 at 21:22
  • It should work, assuming all the migrations are setup right. Logs from the state machines running would help identify any issues, so you should setup logging. – Chris Patterson Oct 27 '20 at 21:40
  • when i changed the TContext for 1 statemachine to sagadbcontext i got the 2 statemachine to work ie i changed r.AddDbContext< **DbContext** , ServiceRequest1UpdateStateDbContext>((provider, builder) => to r.AddDbContext< **SagaDbContext**, ServiceRequest1UpdateStateDbContext>((provider, builder) => ServiceRequest1RegisterStateMachine(DbContext) and ServiceRequest1UpdateStateMachine (SagaDbContext)started to work. Whereas ServiceRequest1ApproveStateMachine(DbContext) still does not work – Rahul Jacob Oct 29 '20 at 20:21
  • I think it's because you're doing this: `` -- take out the `DbContext,` and just register the state db context for each one. Casting it back to DbContext results in a single container registration for that service type. – Chris Patterson Oct 29 '20 at 21:41
  • I was talking about the AddDbContext inside AddSagaStateMachine . My understanding was that the definition of AddDbContext is as below: `void AddDbContext(Action> optionsAction = null) where TContext : DbContext` If there is another way to implement it, can you please explain that – Rahul Jacob Oct 30 '20 at 08:15
  • It looks like changing `AddDbContext` to `ExistingDbContext()` did the trick. Need to do a round of testing to confirm. Also, the casting was done based on the code samples provided in the EFCore persistence docs. Do you think that implementation is still valid? – Rahul Jacob Nov 02 '20 at 10:51

1 Answers1

0

So below are the changes i did to ensure the statemachine works as expected. The changes were done based on @Chris suggestion in comments, even though our initial implementation of the statemachine was based on the docs on sqlserver efcore persistence.

services.AddDbContext<ServiceRequest1RegisterStateDbContext>(x =>
                     x.UseSqlServer(configuration.GetConnectionString("StateDBConnection")));
        services.AddDbContext<ServiceRequest1UpdateStateDbContext>(x =>
                       x.UseSqlServer(configuration.GetConnectionString("StateDBConnection")));
        services.AddDbContext<ServiceRequest1ApproveStateDbContext>(x =>
                        x.UseSqlServer(configuration.GetConnectionString("StateDBConnection")));

services.AddMassTransit(x =>
    {
        x.AddBus(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
        {
            cfg.Host(hostUri, hst =>
            {
                hst.Username(appSettings.RabbitMQ.Username);
                hst.Password(appSettings.RabbitMQ.Password);
            });

            cfg.ReceiveEndpoint("microservice-response", e =>
            {
                AddConsumers(e, provider);
                e.ConfigureSaga<ServiceRequest1RegisterState>(provider);
                e.ConfigureSaga<ServiceRequest1UpdateState>(provider);
                e.ConfigureSaga<ServiceRequest1ApproveState>(provider);
            });
        }));

        ////x.AddSagaStateMachine<ServiceRequest1RegisterStateMachine, ServiceRequest1RegisterState>()
        ////   .InMemoryRepository();
        ////x.AddSagaStateMachine<ServiceRequest1UpdateStateMachine, ServiceRequest1UpdateState>()
        ////   .InMemoryRepository();
        ////x.AddSagaStateMachine<ServiceRequest1ApproveStateMachine, ServiceRequest1ApproveState>()
        ////   .InMemoryRepository();

        x.AddSagaStateMachine<ServiceRequest1RegisterStateMachine, ServiceRequest1RegisterState>()
            .EntityFrameworkRepository(r =>
            {
                r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                r.ExistingDbContext<ServiceRequest1RegisterStateDbContext>();
            });

x.AddSagaStateMachine<ServiceRequest1UpdateStateMachine, ServiceRequest1UpdateState>()
            .EntityFrameworkRepository(r =>
            {
                r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                r.ExistingDbContext<ServiceRequest1UpdateStateDbContext>();
            });

        x.AddSagaStateMachine<ServiceRequest1ApproveStateMachine, ServiceRequest1ApproveState>()
            .EntityFrameworkRepository(r =>
            {
                r.ConcurrencyMode = ConcurrencyMode.Pessimistic; // or use Optimistic, which requires RowVersion

                r.ExistingDbContext<ServiceRequest1ApproveStateDbContext>();
            });
    });

    services.AddSingleton<IHostedService, MassTransitBusService>();

}

Rahul Jacob
  • 19
  • 1
  • 5