Example #1
0
        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);
        }
Example #2
0
        private async Task <IdEntity> GetIdRow(string sequence, string aggregateId)
        {
            var op          = TableOperation.Retrieve <IdEntity>(IdEntity.FormatPartitionKey(sequence), IdEntity.FormatRowKey(aggregateId));
            var tableResult = await table.ExecuteAsync(op);

            return(tableResult.Result as IdEntity);
        }