Exemplo n.º 1
0
        public async Task ConcurentAutoNrGenerationForTheSameDocumentWorksAsExpectedWhenTwoServicesAsksForNumber()
        {
            var tableName = RandomTableNameName();

            Console.WriteLine($"Table name for this test: {tableName}");

            var service1 = new TableStorageAutoNrService(connString, tableName);
            var service2 = new TableStorageAutoNrService(connString, tableName);
            var service3 = new TableStorageAutoNrService(connString, tableName);

            service1.Init();
            service2.Init();
            service3.Init();

            var offer1 = new TestOffer()
            {
                Date = new DateTime(2015, 11, 21)
            };
            var offer2 = new TestOffer()
            {
                Date = new DateTime(2015, 11, 22)
            };

            var generator = new AutoNrGenerator <SequenceData, NrData>((sequenceNewNr, config, prev) =>
            {
                var sequence = $"OFF-{offer2.Date.Year}/{offer2.Date.Month}/";
                var nr       = $"{sequence}{sequenceNewNr:d5}";
                return(new AutoNrResult <SequenceData, NrData>(
                           config ?? new SequenceData(),
                           new NrData()
                {
                    DocumentNr = nr
                }));
            });

            var nr1 = await service1.GetAutoNr <SequenceData, NrData>(
                "rcsoffers",
                offer1.Id.Value,
                generator);

            NrData nr2 = null;

            var nr3 = await service3.GetAutoNr <SequenceData, NrData>(
                "rcsoffers",
                offer2.Id.Value,
                (sequenceNewNr, config, prev) =>
            {
                nr2 = service2.GetAutoNr <SequenceData, NrData>("rcsoffers", offer2.Id.Value, generator).Result;
                return(generator(sequenceNewNr, config, prev));
            });

            Assert.AreEqual("OFF-2015/11/00001", nr1.DocumentNr);
            Assert.AreEqual(nr2.DocumentNr, nr3.DocumentNr);
            Assert.AreEqual("OFF-2015/11/00002", nr2.DocumentNr);
        }
Exemplo n.º 2
0
        public async Task GetAutoNrGeneratesUniqueNrsTest()
        {
            var tableName = RandomTableNameName();

            Console.WriteLine($"Table name for this test: {tableName}");

            var service = new TableStorageAutoNrService(connString, tableName);

            service.Init();

            var offers = Enumerable.Range(1, 30).Select(day => new TestOffer()
            {
                Date = new DateTime(2015, 11, day)
            }).ToArray();
            var nrs = new NrData[30];

            for (int i = 0; i < 30; i++)
            {
                var offer     = offers[i];
                var generator = new AutoNrGenerator <SequenceData, NrData>((sequenceNewNr, config, prev) =>
                {
                    var sequence = $"OFF-{offer.Date.Year}/{offer.Date.Month}/";
                    var nr       = $"{sequence}{sequenceNewNr:d5}";
                    return(new AutoNrResult <SequenceData, NrData>(
                               config ?? new SequenceData(),
                               new NrData()
                    {
                        DocumentNr = nr
                    }));
                });
                nrs[i] = await service.GetAutoNr("rcsoffers", offer.Id.Value, generator);

                //Console.WriteLine(nrs[i]);
            }
            for (int i = 0; i < 30; i++)
            {
                Assert.AreEqual($"OFF-2015/11/{i + 1:d5}", nrs[i].DocumentNr);
            }
        }
Exemplo n.º 3
0
 public async Task <TAutoNrData> GetAutoNr <TSequenceData, TAutoNrData>(string sequence, string aggregateId, AutoNrGenerator <TSequenceData, TAutoNrData> generator)
     where TSequenceData : class
     where TAutoNrData : class
 {
     return(await GetAutoNrWithRetries(sequence, aggregateId, generator, MaxRetries));
 }
Exemplo n.º 4
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);
        }
Exemplo n.º 5
0
        public Task <TNrData> GetAutoNr <TSequenceData, TNrData>(string sequence, string aggregateId, AutoNrGenerator <TSequenceData, TNrData> generator)
            where TSequenceData : class
            where TNrData : class
        {
            Sequence contextLock = sequences.GetOrAdd(sequence, Sequence.Create <TSequenceData>(null));

            lock (contextLock)             // only one thread per context
            {
                var  context = sequences[sequence];
                long seqNr;
                if (context.TryGet(aggregateId, out seqNr))
                {
                    return(Task.FromResult(context.GetAutoNrDataByNr <TNrData>(seqNr)));
                }
                else
                {
                    var prev     = context.GetAutoNrDataByNr <TNrData>(context.LastSequenceNr);
                    var newSeqNr = context.LastSequenceNr + 1;
                    var result   = generator(newSeqNr, context.GetConfig <TSequenceData>(), prev);

                    context.AddNr(aggregateId, newSeqNr, result.NrData);
                    context.SetConfig(result.SequenceData);
                    context.LastSequenceNr = newSeqNr;
                    return(Task.FromResult(result.NrData));
                }
            }
        }