private async Task <TAutoNrData> UpdateAutoNrWithRetries <TSequenceData, TAutoNrData>(string sequence, string aggregateId, AutoNrUpdater <TSequenceData, TAutoNrData> updater, int retries) where TSequenceData : class where TAutoNrData : class { var sequenceRow = await GetSequenceRow(sequence); // or null if (sequenceRow == null) { throw new Exception($"Sequence not found: {sequence}"); } var idRow = await GetIdRow(sequence, aggregateId); if (idRow == null) { throw new Exception($"Agreggate with id {aggregateId} not found in sequence {sequence}"); } var nrRow = await GetNrRow(sequence, idRow.Nr); if (nrRow == null) { throw new Exception($"Agreggate with id {aggregateId} not found in sequence {sequence}"); } var prevNrRow = await GetNrRow(sequence, idRow.Nr - 1); var nextNrRow = await GetNrRow(sequence, idRow.Nr + 1); var result = updater( nrRow.GetData <TAutoNrData>(), sequenceRow.GetData <TSequenceData>(), prevNrRow != null ? prevNrRow.GetData <TAutoNrData>() : null, nextNrRow != null ? nextNrRow.GetData <TAutoNrData>() : null); sequenceRow.SetData(result.SequenceData); nrRow.SetData(result.NrData); var batch = new TableBatchOperation(); batch.Add(TableOperation.Replace(sequenceRow)); // OperationIndex 0 batch.Add(TableOperation.Replace(nrRow)); // OperationIndex 1 if (prevNrRow != null) { batch.Add(TableOperation.Merge(prevNrRow)); // OperationIndex 2 } if (nextNrRow != null) { batch.Add(TableOperation.Merge(nextNrRow)); // OperationIndex 2 or 3 } try { await table.ExecuteBatchAsync(batch); } catch (StorageException storageException) { int operationIndex; string errorCode; if (!TryParseStorageException(storageException, out operationIndex, out errorCode)) { throw; } if (errorCode == "UpdateConditionNotSatisfied") { if (retries >= 0) { return(await UpdateAutoNrWithRetries(sequence, aggregateId, updater, retries - 1)); } var rowName = new string[] { "sequence", "auto-nr", "prev-auto-nr", "next-auto-nr" }[operationIndex]; throw new AutoNrOptimisticException($"Optimistic lock failed because {rowName} has changed. Sequence: {sequence}, nr:{idRow.Nr}."); } throw; } return(result.NrData); }
public async Task <TAutoNrData> UpdateAutoNr <TSequenceData, TAutoNrData>(string sequence, string aggregateId, AutoNrUpdater <TSequenceData, TAutoNrData> updater) where TSequenceData : class where TAutoNrData : class { return(await UpdateAutoNrWithRetries(sequence, aggregateId, updater, MaxRetries)); }
public Task <TNrData> UpdateAutoNr <TSequenceData, TNrData>(string sequence, string aggregateId, AutoNrUpdater <TSequenceData, TNrData> updater) where TSequenceData : class where TNrData : class { Sequence contextLock = sequences[sequence]; if (contextLock == null) { throw new Exception($"Sequence not found: {sequence}"); } lock (contextLock) // only one thread per context { var context = sequences[sequence]; if (context == null) { throw new Exception($"Sequence not found: {sequence}"); } long seqNr; if (!context.TryGet(aggregateId, out seqNr)) { throw new Exception($"Agreggate with id {aggregateId} not found in sequence {sequence}"); } var data = context.GetAutoNrDataByNr <TNrData>(seqNr); var prev = context.GetAutoNrDataByNr <TNrData>(seqNr - 1); var next = context.GetAutoNrDataByNr <TNrData>(seqNr + 1); var result = updater(data, context.GetConfig <TSequenceData>(), prev, next); context.SetData(aggregateId, seqNr, result.NrData); context.SetConfig(result.SequenceData); return(Task.FromResult(result.NrData)); } }