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); }
public async Task Send(SagaConsumeContext <TSaga, TMessage> context) { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Added {2}", TypeMetadataCache <TSaga> .ShortName, context.Saga.CorrelationId, TypeMetadataCache <TMessage> .ShortName); } var proxy = new EntityFrameworkSagaConsumeContext <TSaga, TMessage>(_dbContext, context, context.Saga, false); 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 SendToInstance <T>(SagaQueryConsumeContext <TSaga, T> context, DbContext dbContext, ISagaPolicy <TSaga, T> policy, TSaga instance, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, instance.CorrelationId, TypeMetadataCache <T> .ShortName); } var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); 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); } }
private 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 <T>(dbContext, instance, context.CancellationToken).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) { IQueryable <TSaga> queryable = QuerySagas(dbContext); // 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 queryable.FromSql(rowLockQuery, new object[] { sagaId }).SingleOrDefaultAsync(context.CancellationToken).ConfigureAwait(false); } else { instance = await queryable.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 { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, instance.CorrelationId, TypeMetadataCache <T> .ShortName); } var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } await dbContext.SaveChangesAsync(context.CancellationToken).ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateConcurrencyException) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } catch (DbUpdateException ex) { if (IsDeadlockException(ex)) { // deadlock, no need to rollback } else { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; using (var dbContext = _sagaDbContextFactory()) using (var transaction = await dbContext.Database.BeginTransactionAsync(_isolationLevel, context.CancellationToken).ConfigureAwait(false)) { if (!_optimistic) { // Hack for locking row for the duration of the transaction. var tableName = dbContext.GetTableName <TSaga>(); await dbContext.Database.ExecuteSqlCommandAsync( $"select 1 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", new object[] { sagaId }, context.CancellationToken).ConfigureAwait(false); } var inserted = false; TSaga instance; if (policy.PreInsertInstance(context, out instance)) { inserted = await PreInsertSagaInstance <T>(dbContext, instance, context.CancellationToken).ConfigureAwait(false); } try { if (instance == null) { instance = await QuerySagas <T>(dbContext).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 { if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, instance.CorrelationId, TypeMetadataCache <T> .ShortName); } var sagaConsumeContext = new EntityFrameworkSagaConsumeContext <TSaga, T>(dbContext, context, instance); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } await dbContext.SaveChangesAsync(context.CancellationToken).ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateConcurrencyException ex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } catch (DbUpdateException ex) { if (IsDeadlockException(ex)) { // deadlock, no need to rollback } else { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } } }