public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = await _collection.Find(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { context.LogFault(sex); throw; } catch (Exception ex) { context.LogFault(ex); throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } TSaga instance; if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance(context, instance).ConfigureAwait(false); } if (instance == null) { instance = await _collection.Find(x => x.CorrelationId == context.CorrelationId).SingleOrDefaultAsync(context.CancellationToken).ConfigureAwait(false); } if (instance == null) { var missingSagaPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
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)); } Guid sagaId = context.CorrelationId.Value; using (DbContext dbContext = _sagaDbContextFactory()) using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { bool 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 = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == sagaId); } 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); // if (inserted && !sagaConsumeContext.IsCompleted) // dbContext.Set<TSaga>().Update(instance); } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (Exception) { transaction.Rollback(); 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 session = _store.DirtyTrackedSession()) { TSaga instance; if (policy.PreInsertInstance(context, out instance)) { PreInsertSagaInstance <T>(session, instance); } if (instance == null) { instance = session.Load <TSaga>(sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } }
public async Task Send(SagaRepositoryQueryContext <TSaga, T> context) { if (context.Count > 0) { async Task LoadInstance(Guid correlationId) { SagaConsumeContext <TSaga, T> sagaConsumeContext = await context.Load(correlationId).ConfigureAwait(false); if (sagaConsumeContext != null) { sagaConsumeContext.LogUsed(); try { await _policy.Existing(sagaConsumeContext, _next).ConfigureAwait(false); if (_policy.IsReadOnly) { await context.Undo(sagaConsumeContext).ConfigureAwait(false); } else { if (sagaConsumeContext.IsCompleted) { await context.Delete(sagaConsumeContext).ConfigureAwait(false); sagaConsumeContext.LogRemoved(); } else { await context.Update(sagaConsumeContext).ConfigureAwait(false); } } } finally { switch (sagaConsumeContext) { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync().ConfigureAwait(false); break; case IDisposable disposable: disposable.Dispose(); break; } } } } await Task.WhenAll(context.Select(LoadInstance)).ConfigureAwait(false); } else { var missingPipe = new MissingSagaPipe <TSaga, T>(context, _next); await _policy.Missing(context, missingPipe).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = _sessionFactory.OpenSession()) using (var transaction = session.BeginTransaction()) { try { IList <TSaga> instances = await session.QueryOver <TSaga>() .Where(context.Query.FilterExpression) .ListAsync <TSaga>() .ConfigureAwait(false); if (instances.Count == 0) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await Task.WhenAll(instances.Select(instance => SendToInstance(context, policy, instance, next, session))).ConfigureAwait(false); } // TODO partial failure should not affect them all if (transaction.IsActive) { await transaction.CommitAsync().ConfigureAwait(false); } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } if (transaction.IsActive) { await transaction.RollbackAsync().ConfigureAwait(false); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } if (transaction.IsActive) { await transaction.RollbackAsync().ConfigureAwait(false); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } _logger.Debug($"SAGA: Send {context.Message.GetType().FullName}"); var sagaId = context.CorrelationId.Value; if (policy.PreInsertInstance(context, out var instance)) { await PreInsertSagaInstance <T>(instance, context.MessageId).ConfigureAwait(false); } if (instance == null) { instance = await GetSaga(sagaId); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(_connection, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
public async Task Send(SagaRepositoryContext <TSaga, T> context) { SagaConsumeContext <TSaga, T> sagaConsumeContext = null; if (_policy.PreInsertInstance(context, out var instance)) { sagaConsumeContext = await context.Insert(instance).ConfigureAwait(false); } sagaConsumeContext ??= await context.Load(_correlationId).ConfigureAwait(false); if (sagaConsumeContext != null) { try { sagaConsumeContext.LogUsed(); await _policy.Existing(sagaConsumeContext, _next).ConfigureAwait(false); if (_policy.IsReadOnly) { await context.Undo(sagaConsumeContext).ConfigureAwait(false); } else { if (sagaConsumeContext.IsCompleted) { await context.Delete(sagaConsumeContext).ConfigureAwait(false); sagaConsumeContext.LogRemoved(); } else { await context.Update(sagaConsumeContext).ConfigureAwait(false); } } } finally { switch (sagaConsumeContext) { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync().ConfigureAwait(false); break; case IDisposable disposable: disposable.Dispose(); break; } } } else { await _policy.Missing(context, new MissingSagaPipe <TSaga, T>(context, _next)).ConfigureAwait(false); } }
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; var needToLeaveSagas = true; await _sagas.MarkInUse(context.CancellationToken).ConfigureAwait(false); try { SagaInstance <TSaga> saga = _sagas[sagaId]; if (saga == null) { var missingSagaPipe = new MissingPipe <T>(this, next, true); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); _sagas.Release(); needToLeaveSagas = false; } else { await saga.MarkInUse(context.CancellationToken).ConfigureAwait(false); try { _sagas.Release(); needToLeaveSagas = false; if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Used {2}", TypeMetadataCache <TSaga> .ShortName, sagaId, TypeMetadataCache <T> .ShortName); } SagaConsumeContext <TSaga, T> sagaConsumeContext = new InMemorySagaConsumeContext <TSaga, T>(context, saga.Instance, () => Remove(saga, context.CancellationToken)); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); } finally { saga.Release(); } } } finally { if (needToLeaveSagas) { _sagas.Release(); } } }
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 correlationId = context.CorrelationId.Value; if (policy.PreInsertInstance(context, out TSaga preInsertInstance)) { using (var connection = new SqlConnection(_connectionString)) { await PreInsertSagaInstance <T>(connection, context, preInsertInstance).ConfigureAwait(false); } } using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) using (var connection = new SqlConnection(_connectionString)) { try { var tableName = GetTableName(); var instance = await connection.QuerySingleOrDefaultAsync <TSaga>( $"SELECT * FROM {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @correlationId", new { correlationId }).ConfigureAwait(false); if (instance == null) { var missingSagaPipe = new MissingPipe <T>(connection, tableName, next, InsertSagaInstance); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, connection, policy, instance, tableName, next).ConfigureAwait(false); } transaction.Complete(); } catch (SagaException sex) { LogContext.Error?.Log(sex, "SAGA:{SagaType} Exception {MessageType}", TypeMetadataCache <TSaga> .ShortName, TypeMetadataCache <T> .ShortName); throw; } catch (Exception ex) { LogContext.Error?.Log(ex, "SAGA:{SagaType} Exception {MessageType}", TypeMetadataCache <TSaga> .ShortName, TypeMetadataCache <T> .ShortName); throw new SagaException(ex.Message, typeof(TSaga), typeof(T), correlationId, ex); } } }
Task ISagaRepository <TSaga> .SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { SagaInstance <TSaga>[] existingSagas = _sagas.Where(context.Query).ToArray(); if (existingSagas.Length == 0) { var missingSagaPipe = new MissingPipe <T>(this, next); return(policy.Missing(context, missingSagaPipe)); } return(Task.WhenAll(existingSagas.Select(instance => SendToInstance(context, policy, instance, next)))); }
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; var db = _redisDbFactory(); ITypedDatabase <TSaga> sagas = db.As <TSaga>(); IAsyncDisposable pessimisticLock = null; if (!_optimistic) { pessimisticLock = await db.AcquireLockAsync(sagaId, _lockTimeout, _lockRetryTimeout).ConfigureAwait(false); } try { if (policy.PreInsertInstance(context, out var instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { instance = await sagas.Get(sagaId).ConfigureAwait(false); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(sagas, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(sagas, context, policy, next, instance).ConfigureAwait(false); } } finally { if (!_optimistic) { await pessimisticLock.DisposeAsync().ConfigureAwait(false); } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) using (var connection = new SqlConnection(_connectionString)) { try { var tableName = GetTableName <T>(); var(whereStatement, parameters) = WhereStatementHelper.GetWhereStatementAndParametersFromExpression(context.Query.FilterExpression); List <TSaga> instances = (await connection.QueryAsync <TSaga>($"SELECT * FROM {tableName} WITH (UPDLOCK, ROWLOCK) {whereStatement}", parameters).ConfigureAwait(false)).ToList(); if (!instances.Any()) { var missingSagaPipe = new MissingPipe <T>(connection, tableName, next, InsertSagaInstance); await policy.Missing(context, missingSagaPipe); } else { foreach (var instance in instances) { await SendToInstance(context, connection, policy, instance, tableName, next).ConfigureAwait(false); } } transaction.Complete(); } catch (SagaException sex) { if (Log.IsErrorEnabled) { Log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (Log.IsErrorEnabled) { Log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var dbContext = _sagaDbContextFactory()) using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { try { var sagaInstances = await dbContext.Set <TSaga>().Where(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (sagaInstances.Count == 0) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, dbContext, policy, instance, next).ConfigureAwait(false); } } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (SagaException sex) { transaction.Rollback(); if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { transaction.Rollback(); if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = (await _client .CreateDocumentQuery <TSaga>(UriFactory.CreateDocumentCollectionUri(_databaseName, _collectionName), _feedOptions) .Where(context.Query.FilterExpression).QueryAsync().ConfigureAwait(false)).ToList(); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _requestOptions); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { // To support optimistic concurrency, we need the Document ETag // So we will re-query to get the document, this should be fast because we are querying by Id var response = await _client .ReadDocumentAsync(UriFactory.CreateDocumentUri(_databaseName, _collectionName, instance.CorrelationId.ToString()), _requestOptions) .ConfigureAwait(false); await SendToInstance(context, policy, next, instance, response.Resource).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } Document document = null; if (policy.PreInsertInstance(context, out var instance)) { document = await PreInsertSagaInstance(context, instance).ConfigureAwait(false); } if (instance == null) { try { ResourceResponse <Document> response = await _client .ReadDocumentAsync(UriFactory.CreateDocumentUri(_databaseName, _collectionName, context.CorrelationId.ToString()), _requestOptions) .ConfigureAwait(false); document = response.Resource; } catch (DocumentClientException e) when(e.StatusCode == System.Net.HttpStatusCode.NotFound) { // Couldn't find the document, swallowing exception } if (document != null) { instance = JsonConvert.DeserializeObject <TSaga>(document.ToString()); } } if (instance == null) { var missingSagaPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _requestOptions); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance, document).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = OpenSession()) { try { var guids = (await Find(context.Query).ConfigureAwait(false)).ToArray(); if (!guids.Any()) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { var ids = guids.Select(x => ConvertToSagaId(session, x)).ToArray(); var instances = await session.LoadAsync <TSaga>(ids); foreach (var instance in instances) { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } await session.SaveChangesAsync(); } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error( $"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { if (!context.TryGetPayload(out MessageSessionContext sessionContext)) { throw new SagaException($"The session-based saga repository requires an active message session: {TypeMetadataCache<TSaga>.ShortName}", typeof(TSaga), typeof(T)); } if (Guid.TryParse(sessionContext.SessionId, out var sessionId)) { context = new CorrelationIdConsumeContextProxy <T>(context, sessionId); } StartedActivity?activity = LogContext.IfEnabled(OperationName.Saga.Send)?.StartSagaActivity <TSaga, T>(context); try { var saga = await ReadSagaState(sessionContext).ConfigureAwait(false); if (saga == null) { var missingSagaPipe = new MissingPipe <T>(next, WriteSagaState); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { SagaConsumeContext <TSaga, T> sagaConsumeContext = new MessageSessionSagaConsumeContext <TSaga, T>(context, sessionContext, saga); LogContext.Debug?.Log("SAGA:{SagaType}:{CorrelationId} Used {MessageType}", TypeMetadataCache <TSaga> .ShortName, context.CorrelationId, TypeMetadataCache <T> .ShortName); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); if (!sagaConsumeContext.IsCompleted) { await WriteSagaState(sessionContext, saga).ConfigureAwait(false); LogContext.Debug?.Log("SAGA:{SagaType}:{CorrelationId} Updated {MessageType}", TypeMetadataCache <TSaga> .ShortName, context.CorrelationId, TypeMetadataCache <T> .ShortName); } } } finally { activity?.Stop(); } }
async Task ISagaRepository <TSaga> .Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) { MessageSessionContext sessionContext; if (!context.TryGetPayload(out sessionContext)) { throw new SagaException($"The session-based saga repository requires an active message session: {TypeMetadataCache<TSaga>.ShortName}", typeof(TSaga), typeof(T)); } Guid sessionId; if (Guid.TryParse(sessionContext.SessionId, out sessionId)) { context = new CorrelationIdConsumeContextProxy <T>(context, sessionId); } var saga = await ReadSagaState(sessionContext).ConfigureAwait(false); if (saga == null) { var missingSagaPipe = new MissingPipe <T>(next, WriteSagaState); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { SagaConsumeContext <TSaga, T> sagaConsumeContext = new MessageSessionSagaConsumeContext <TSaga, T>(context, sessionContext, saga); if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Existing {2}", TypeMetadataCache <TSaga> .ShortName, sessionContext.SessionId, TypeMetadataCache <T> .ShortName); } await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); if (!sagaConsumeContext.IsCompleted) { await WriteSagaState(sessionContext, saga).ConfigureAwait(false); if (_log.IsDebugEnabled) { _log.DebugFormat("SAGA:{0}:{1} Updated {2}", TypeMetadataCache <TSaga> .ShortName, sessionContext.SessionId, TypeMetadataCache <T> .ShortName); } } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var session = _store.DirtyTrackedSession()) { try { IEnumerable <TSaga> instances = await session.Query <TSaga>() .Where(context.Query.FilterExpression) .ToListAsync().ConfigureAwait(false); if (!instances.Any()) { var missingSagaPipe = new MissingPipe <T>(session, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { foreach (var instance in instances) { await SendToInstance(context, policy, instance, next, session).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } }
public async Task Send(SagaRepositoryContext <TSaga, T> context) { SagaConsumeContext <TSaga, T> sagaConsumeContext = null; if (_policy.PreInsertInstance(context, out var instance)) { sagaConsumeContext = await context.Insert(instance).ConfigureAwait(false); } if (sagaConsumeContext == null) { sagaConsumeContext = await context.Load(_correlationId).ConfigureAwait(false); } if (sagaConsumeContext != null) { try { sagaConsumeContext.LogUsed(); await _policy.Existing(sagaConsumeContext, _next).ConfigureAwait(false); } finally { switch (sagaConsumeContext) { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync().ConfigureAwait(false); break; case IDisposable disposable: disposable.Dispose(); break; } } } else { var missingPipe = new MissingSagaPipe <TSaga, T>(context, _next); await _policy.Missing(context, missingPipe).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { var sagaInstances = (await _client .CreateDocumentQuery <TSaga>(UriFactory.CreateDocumentCollectionUri(_databaseName, _collectionName), _feedOptions) .Where(context.Query.FilterExpression).QueryAsync().ConfigureAwait(false)).ToList(); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_client, _databaseName, _collectionName, next, _documentDbSagaConsumeContextFactory, _jsonSerializerSettings); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } throw; } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; var db = _redisConnection.GetDatabase(); TSaga instance; ITypedDatabase <TSaga> sagas = db.As <TSaga>(); if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { using (var distLock = _lockFactory.Create($"redislock:{context.CorrelationId.Value}", _expiry, _wait, _retryTime)) { if (distLock.IsAcquired) { instance = await sagas.Get(sagaId, _redisPrefix).ConfigureAwait(false); } } } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(db, _lockFactory, next, _redisPrefix); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { List <TSaga> sagaInstances = await _collection.Find(context.Query.FilterExpression).ToListAsync().ConfigureAwait(false); if (!sagaInstances.Any()) { var missingPipe = new MissingPipe <TSaga, T>(_collection, next, _mongoDbSagaConsumeContextFactory); await policy.Missing(context, missingPipe).ConfigureAwait(false); } else { foreach (var instance in sagaInstances) { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } } } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }
public async Task Send <T>(ConsumeContext <T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { if (!context.CorrelationId.HasValue) { throw new SagaException("The CorrelationId was not specified", typeof(TSaga), typeof(T)); } var sagaId = context.CorrelationId.Value; TSaga instance; using (var redis = _clientsManager.GetClient()) { var sagas = redis.As <TSaga>(); if (policy.PreInsertInstance(context, out instance)) { await PreInsertSagaInstance <T>(sagas, instance).ConfigureAwait(false); } if (instance == null) { instance = sagas.GetById(sagaId); } } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(_clientsManager, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await SendToInstance(context, policy, next, instance).ConfigureAwait(false); } }
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 = dbContext.Database.BeginTransaction(_isolationLevel)) { // Hack for locking row for the duration of the transaction. var tableName = ((IObjectContextAdapter)dbContext).ObjectContext.CreateObjectSet <TSaga>().EntitySet.Name; await dbContext.Database.ExecuteSqlCommandAsync($"select 1 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", sagaId) .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 = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == sagaId); } 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().ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateException ex) { var baseException = ex.GetBaseException() as SqlException; if (baseException != null && baseException.Number == 1205) { // deadlock, no need to rollback } else { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (Exception) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var dbContext = _sagaDbContextFactory()) { // We just get the correlation ids related to our Filter. // We do this outside of the transaction to make sure we don't create a range lock. var correlationIds = await dbContext.Set <TSaga>().Where(context.Query.FilterExpression) .Select(x => x.CorrelationId) .ToListAsync() .ConfigureAwait(false); using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { try { if (correlationIds.Count == 0) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { var tableName = ((IObjectContextAdapter)dbContext).ObjectContext.CreateObjectSet <TSaga>().EntitySet.Name; foreach (var correlationId in correlationIds) { // Hack for locking row for the duration of the transaction. // We only lock one at a time, since we don't want an accidental range lock. await dbContext.Database.ExecuteSqlCommandAsync( $"select 2 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", correlationId).ConfigureAwait(false); var instance = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == correlationId); await SendToInstance(context, dbContext, policy, instance, next).ConfigureAwait(false); } } await dbContext.SaveChangesAsync().ConfigureAwait(false); transaction.Commit(); } catch (DbUpdateException ex) { var baseException = ex.GetBaseException() as SqlException; if (baseException != null && baseException.Number == 1205) { // deadlock, no need to rollback } else { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (SagaException sex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error("Saga Exception Occurred", sex); } } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } } }
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 session = _sessionFactory.OpenSession()) using (var transaction = session.BeginTransaction()) { var inserted = false; TSaga instance; if (policy.PreInsertInstance(context, out instance)) { inserted = PreInsertSagaInstance <T>(session, instance); } try { if (instance == null) { instance = session.Get <TSaga>(sagaId, LockMode.Upgrade); } if (instance == null) { var missingSagaPipe = new MissingPipe <T>(session, 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 NHibernateSagaConsumeContext <TSaga, T>(session, context, instance); await policy.Existing(sagaConsumeContext, next).ConfigureAwait(false); if (inserted && !sagaConsumeContext.IsCompleted) { session.Update(instance); } } if (transaction.IsActive) { transaction.Commit(); } } catch (Exception ex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } if (transaction.IsActive) { transaction.Rollback(); } throw; } } }
public async Task SendQuery <T>(SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { using (var dbContext = _sagaDbContextFactory()) { // We just get the correlation ids related to our Filter. // We do this outside of the transaction to make sure we don't create a range lock. List <Guid> correlationIds = await dbContext.Set <TSaga>().Where(context.Query.FilterExpression) .Select(x => x.CorrelationId) .ToListAsync() .ConfigureAwait(false); using (var transaction = dbContext.Database.BeginTransaction(_isolationLevel)) { try { var missingCorrelationIds = new List <Guid>(); if (correlationIds.Any()) { var tableName = _relationalEntityMetadataHelper.GetTableName <TSaga>(dbContext); foreach (var correlationId in correlationIds) { if (!_optimistic) { // Hack for locking row for the duration of the transaction. // We only lock one at a time, since we don't want an accidental range lock. await dbContext.Database.ExecuteSqlCommandAsync( $"select 2 from {tableName} WITH (UPDLOCK, ROWLOCK) WHERE CorrelationId = @p0", correlationId).ConfigureAwait(false); } var instance = dbContext.Set <TSaga>().SingleOrDefault(x => x.CorrelationId == correlationId); if (instance != null) { await SendToInstance(context, dbContext, policy, instance, next).ConfigureAwait(false); } else { missingCorrelationIds.Add(correlationId); } } } // If no sagas are found or all are missing if (correlationIds.Count == missingCorrelationIds.Count) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } await dbContext.SaveChangesAsync().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 { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } } } }
private async Task SendQueryLogic <T>(IList <Guid> nonTrackedInstances, IDbContextTransaction transaction, DbContext dbContext, SagaQueryConsumeContext <TSaga, T> context, ISagaPolicy <TSaga, T> policy, IPipe <SagaConsumeContext <TSaga, T> > next) where T : class { try { // Simple path for Optimistic Concurrency if (_rawSqlLockStatements == null) { var instances = await QuerySagas(dbContext) .Where(context.Query.FilterExpression) .ToListAsync(context.CancellationToken) .ConfigureAwait(false); if (!instances.Any()) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).ConfigureAwait(false); } else { await Task.WhenAll(instances.Select(instance => SendToInstance(context, dbContext, policy, instance, next))).ConfigureAwait(false); } } // Pessimistic Concurrency else { // Hack for locking row for the duration of the transaction. // We only lock one at a time, since we don't want an accidental range lock. var rowLockQuery = _rawSqlLockStatements.GetRowLockStatement <TSaga>(dbContext); var missingCorrelationIds = new List <Guid>(); if (nonTrackedInstances?.Any() == true) { var foundInstances = new List <Task>(); foreach (var nonTrackedInstance in nonTrackedInstances) { // 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 instance = await QuerySagas(dbContext) .FromSql(rowLockQuery, new object[] { nonTrackedInstance }) .SingleOrDefaultAsync(context.CancellationToken) .ConfigureAwait(false); if (instance != null) { foundInstances.Add(SendToInstance(context, dbContext, policy, instance, next)); } else { missingCorrelationIds.Add(nonTrackedInstance); } } if (foundInstances.Any()) { await Task.WhenAll(foundInstances).ConfigureAwait(false); } } // If no sagas are found or all are missing if (nonTrackedInstances.Count == missingCorrelationIds.Count) { var missingSagaPipe = new MissingPipe <T>(dbContext, next); await policy.Missing(context, missingSagaPipe).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 { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } } throw; } catch (SagaException sex) { if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", sex); } try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } throw; } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception innerException) { if (_log.IsWarnEnabled) { _log.Warn("The transaction rollback failed", innerException); } } if (_log.IsErrorEnabled) { _log.Error($"SAGA:{TypeMetadataCache<TSaga>.ShortName} Exception {TypeMetadataCache<T>.ShortName}", ex); } throw new SagaException(ex.Message, typeof(TSaga), typeof(T), Guid.Empty, ex); } }