entity-frameworkmasstransitsaga

MassTransit help registering multiple sagas


I'm working on porting an old app to .net6, and ran into an issue with registrering multiple sagas with masstransit.

services.AddMassTransit<IProcessManagerBus>(busCfg =>
{
    busCfg.AddSagaStateMachine<OrderPM, OrderPMState>()
        .EntityFrameworkRepository<OrderPMState>(efConfig =>
        {
            efConfig.ConcurrencyMode = ConcurrencyMode.Optimistic;
            efConfig.DatabaseFactory(() => new OrderStateDbContext(configuration.GetConnectionString("DB")));
        });
    
    busCfg.AddSagaStateMachine<CsaLoginPM, CsaLoginPMState>()
        .EntityFrameworkRepository<CsaLoginPMState>(efConfig =>
        {
            efConfig.ConcurrencyMode = ConcurrencyMode.Optimistic;
            efConfig.DatabaseFactory(() => new CsaLoginStateDbContext(configuration.GetConnectionString("DB")));
        });
    
    busCfg.UsingRabbitMq((context, rabbitCfg) =>
    {
        rabbitCfg.UseJsonSerializer();
        rabbitCfg.Host(new Uri(configuration.GetValue<string>("messaging:pm-bus:host-address")), hostCfg =>
        {
            hostCfg.Username(configuration.GetValue<string>("messaging:pm-bus:username"));
            hostCfg.Password(configuration.GetValue<string>("messaging:pm-bus:password"));        
            
            rabbitCfg.ReceiveEndpoint(configuration.GetValue<string>("messaging:pm-bus:receive-queue"), epCfg =>
            {
                epCfg.PrefetchCount = 10;
                epCfg.UseRetry(retryConfig => retryConfig.Exponential(5, TimeSpan.FromMilliseconds(500), TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(1)));
                
                epCfg.ConfigureSagas(context);
            });
        });
    });
});

The OrderPMState works fine, but the CsaLoginPMState gives the following error when triggered: System.InvalidOperationException: The entity type CsaLoginPMState is not part of the model for the current context.

If I comment out the registration of the OrderPMState, the CsaLoginPMState works fine. I suspect, that the 2 sagas are using the same DbContext, despite being registered with their individual DbContext.

OrderStateDbContext

public class OrderStateDbContext : SagaDbContext
{
    public OrderStateDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    protected override IEnumerable<ISagaClassMap> Configurations
    {
        get { yield return new OrderPMStateMapping(); }
    }
}

CsaLoginStateDbContext

public class CsaLoginStateDbContext : SagaDbContext
{
    public CsaLoginStateDbContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    protected override IEnumerable<ISagaClassMap> Configurations
    {
        get { yield return new CsaLoginPMStateMapping(); }
    }
}

The old version of the app used AutoFac, and the registration was done like this:

builder.Register(x =>
    EntityFrameworkSagaRepository<OrderPMState>.CreateOptimistic(() => new OrderStateDbContext(_configuration.GetConnectionString("DB"))))
    .As<ISagaRepository<OrderPMState>>().SingleInstance();

builder.Register(x =>
    EntityFrameworkSagaRepository<CsaLoginPMState>.CreateOptimistic(() => new CsaLoginStateDbContext(_configuration.GetConnectionString("DB"))))
    .As<ISagaRepository<CsaLoginPMState>>().SingleInstance();

Am I missing something?


Solution

  • When using a DbContext, you should be using one of the two configuration methods that are specifically designed for use with DbContext.

     busCfg.AddSagaStateMachine<CsaLoginPM, CsaLoginPMState>()
        .EntityFrameworkRepository<CsaLoginPMState>(efConfig =>
        {
            efConfig.ConcurrencyMode = ConcurrencyMode.Optimistic;
    
            efConfig.AddDbContext<DbContext, CsaLoginStateDbContext>((provider,builder) =>
            {
                builder.UseSqlServer(configuration.GetConnectionString("DB"), m =>
                {
                    m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
                    m.MigrationsHistoryTable($"__{nameof(CsaLoginStateDbContext)}");
                });
            });
        });
    

    Or you can add the DbContext separately and use that existing DbContext in the saga:

    
    services.AddDbContext<CsaLoginStateDbContext>(builder =>
        builder.UseSqlServer(configuration.GetConnectionString("DB"), m =>
        {
            m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
            m.MigrationsHistoryTable($"__{nameof(CsaLoginStateDbContext)}");
        }));
    
    services.AddMassTransit(x =>
    {
        x.AddSagaRepository<JobSaga>()
            .EntityFrameworkRepository(r =>
            {
                r.ConcurrencyMode = ConcurrencyMode.Optimistic;
                r.ExistingDbContext<CsaLoginStateDbContext>();
            });
    });
    

    This is all covered in the documentation.