private async Task <NrEntity> GetNrRow(string sequence, long nr) { var op = TableOperation.Retrieve <NrEntity>(NrEntity.FormatPartitionKey(sequence), NrEntity.FormatRowKey(nr)); var tableResult = await table.ExecuteAsync(op); return(tableResult.Result as NrEntity); }
private async Task <TAutoNrData> GetAutoNrWithRetries <TSequenceData, TAutoNrData>(string sequence, string aggregateId, AutoNrGenerator <TSequenceData, TAutoNrData> generator, int retries) where TSequenceData : class where TAutoNrData : class { var sequenceEntity = await GetSequenceRow(sequence); // or null if (sequenceEntity == null) { // No sequence row var newSequenceResult = generator(1L, null, null); var firstBatch = new TableBatchOperation(); firstBatch.Insert(SequenceEntity.Create(sequence, 1L, newSequenceResult.SequenceData)); // OperationIndex 0 firstBatch.Insert(NrEntity.Create(sequence, aggregateId, 1L, newSequenceResult.NrData)); // OperationIndex 1 firstBatch.Insert(IdEntity.Create(sequence, aggregateId, 1L)); // OperationIndex 2 try { await table.ExecuteBatchAsync(firstBatch); } catch (StorageException storageException) { int operationIndex; string errorCode; if (!TryParseStorageException(storageException, out operationIndex, out errorCode)) { throw; } switch (operationIndex) { case 0: // Insert(SequenceEntity) if (errorCode == "EntityAlreadyExists") { if (retries > 0) { return(await GetAutoNrWithRetries(sequence, aggregateId, generator, retries - 1)); } throw new InvalidOperationException($"Optimistic lock failed because sequence row already exists. Sequence: {sequence}, nr:1."); } break; case 1: // Insert(NrEntity) if (errorCode == "EntityAlreadyExists") { throw new DuplicateDocumentNrException(sequence, 1L); } break; case 2: // Insert(IdEntity) if (errorCode == "EntityAlreadyExists") { var loadedIdRow = await GetIdRow(sequence, aggregateId); var loadedNrRow = await GetNrRow(sequence, loadedIdRow.Nr); return(loadedNrRow.GetData <TAutoNrData>()); } break; default: break; } throw; } return(newSequenceResult.NrData); } // sequence row present var prevNrRow = await GetNrRow(sequence, sequenceEntity.LastNr); sequenceEntity.LastNr++; var result = generator( sequenceEntity.LastNr, sequenceEntity.GetData <TSequenceData>(), prevNrRow != null ? prevNrRow.GetData <TAutoNrData>() : null); sequenceEntity.SetData(result.SequenceData); var batch = new TableBatchOperation(); batch.Add(TableOperation.Replace(sequenceEntity)); // OperationIndex 0 batch.Add(TableOperation.Insert(NrEntity.Create(sequence, aggregateId, sequenceEntity.LastNr, result.NrData))); // OperationIndex 1 batch.Add(TableOperation.Insert(IdEntity.Create(sequence, aggregateId, sequenceEntity.LastNr))); // OperationIndex 2 if (prevNrRow != null) { batch.Add(TableOperation.Merge(prevNrRow)); // OperationIndex 3 } try { await table.ExecuteBatchAsync(batch); } catch (StorageException storageException) { int operationIndex; string errorCode; if (!TryParseStorageException(storageException, out operationIndex, out errorCode)) { throw; } switch (operationIndex) { case 0: // Replace(sequenceEntity) case 3: // Merge(prevNrRow) if (errorCode == "UpdateConditionNotSatisfied") { if (retries > 0) { return(await GetAutoNrWithRetries(sequence, aggregateId, generator, retries - 1)); } string reason = operationIndex == 0 ? "sequence row has changed" : "prev-nr row has changed"; throw new AutoNrOptimisticException($"Optimistic lock failed because {reason}. Sequence: {sequence}, nr:{sequenceEntity.LastNr}."); } break; case 1: // Insert(NrEntity) if (errorCode == "EntityAlreadyExists") { throw new DuplicateDocumentNrException(sequence, sequenceEntity.LastNr); } break; case 2: // Insert(IdEntity) if (errorCode == "EntityAlreadyExists") { var loadedIdRow = await GetIdRow(sequence, aggregateId); var loadedNrRow = await GetNrRow(sequence, loadedIdRow.Nr); return(loadedNrRow.GetData <TAutoNrData>()); } break; default: break; } throw; } return(result.NrData); }