public async Task Send(SagaConsumeContext <TSaga, TMessage> context) { var proxy = new EntityFrameworkSagaConsumeContext <TSaga, TMessage>(_dbContext, context, context.Saga, false); proxy.LogAdded(); await _next.Send(proxy).ConfigureAwait(false); if (!proxy.IsCompleted) { _dbContext.Set <TSaga>().Add(context.Saga); } await _dbContext.SaveChangesAsync(context.CancellationToken).ConfigureAwait(false); }
async Task SendToInstance <T>(SagaQueryConsumeContext <TSaga, T> context, DbContext dbContext, ISagaPolicy <TSaga, T> policy, TSaga instance, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); sagaConsumeContext.LogUsed(); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } catch (SagaException) { throw; } catch (Exception ex) { throw new SagaException(ex.Message, typeof(TSaga), typeof(T), instance.CorrelationId, ex); } }
async Task SendLogic <T>(IDbContextTransaction transaction, DbContext dbContext, ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { var sagaId = context.CorrelationId.Value; if (policy.PreInsertInstance(context, out var instance)) { var inserted = await PreInsertSagaInstance(dbContext, context, instance).ConfigureAwait(false); if (!inserted) { instance = null; // Reset this back to null if the insert failed. We will use the MissingPipe to create instead } } try { if (instance == null) { // Query with a row Lock instead using FromSql. Still a single trip to the DB (unlike EF6, which has to make one dummy call to row lock) var rowLockQuery = _rawSqlLockStatements?.GetRowLockStatement <TSaga>(dbContext); if (rowLockQuery != null) { instance = await dbContext.Set <TSaga>().FromSqlRaw(rowLockQuery, new object[] { sagaId }).SingleOrDefaultAsync(context.CancellationToken) .ConfigureAwait(false); } else { instance = await dbContext.Set <TSaga>().SingleOrDefaultAsync(x => x.CorrelationId == sagaId, context.CancellationToken).ConfigureAwait(false); } } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); sagaConsumeContext.LogUsed(); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } await dbContext.SaveChangesAsync(context.CancellationToken).ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateConcurrencyException) { try { transaction.Rollback(); } catch (Exception innerException) { LogContext.Warning?.Log(innerException, "Transaction rollback failed"); } throw; } catch (DbUpdateException ex) { if (IsDeadlockException(ex)) { // deadlock, no need to rollback } else { context.LogFault(this, ex, instance?.CorrelationId); try { transaction.Rollback(); } catch (Exception innerException) { LogContext.Warning?.Log(innerException, "Transaction rollback failed"); } } throw; } catch (Exception ex) { context.LogFault(this, ex, instance?.CorrelationId); try { transaction.Rollback(); } catch (Exception innerException) { LogContext.Warning?.Log(innerException, "Transaction rollback failed"); } throw; } }