public async Task Save_should_fail_when_data_changes_between_read_and_update()
        {
            var correlationPropertyData = Guid.NewGuid().ToString();

            var persister = configuration.SagaStorage;
            var insertContextBag = configuration.GetContextBagForSagaStorage();
            Guid generatedSagaId;
            using (var insertSession = await configuration.SynchronizedStorage.OpenSession(insertContextBag))
            {
                var sagaData = new TestSagaData { SomeId = correlationPropertyData, DateTimeProperty = DateTime.UtcNow };
                var correlationProperty = SetActiveSagaInstanceForSave(insertContextBag, new TestSaga(), sagaData);
                generatedSagaId = sagaData.Id;

                await persister.Save(sagaData, correlationProperty, insertSession, insertContextBag);
                await insertSession.CompleteAsync();
            }

            ContextBag losingContext;
            CompletableSynchronizedStorageSession losingSaveSession;
            TestSagaData staleRecord;

            var winningContext = configuration.GetContextBagForSagaStorage();
            var winningSaveSession = await configuration.SynchronizedStorage.OpenSession(winningContext);
            try
            {
                SetActiveSagaInstanceForGet<TestSaga, TestSagaData>(winningContext, new TestSagaData { Id = generatedSagaId, SomeId = correlationPropertyData });
                var record = await persister.Get<TestSagaData>(generatedSagaId, winningSaveSession, winningContext);
                SetActiveSagaInstanceForGet<TestSaga, TestSagaData>(winningContext, record);

                losingContext = configuration.GetContextBagForSagaStorage();
                losingSaveSession = await configuration.SynchronizedStorage.OpenSession(losingContext);
                SetActiveSagaInstanceForGet<TestSaga, TestSagaData>(losingContext, new TestSagaData { Id = generatedSagaId, SomeId = correlationPropertyData });
                staleRecord = await persister.Get<TestSagaData>("SomeId", correlationPropertyData, losingSaveSession, losingContext);
                SetActiveSagaInstanceForGet<TestSaga, TestSagaData>(losingContext, staleRecord);

                record.DateTimeProperty = DateTime.UtcNow;
                await persister.Update(record, winningSaveSession, winningContext);
                await winningSaveSession.CompleteAsync();
            }
            finally
            {
                winningSaveSession.Dispose();
            }

            try
            {
                await persister.Update(staleRecord, losingSaveSession, losingContext);
                Assert.That(async () => await losingSaveSession.CompleteAsync(), Throws.InstanceOf<Exception>().And.Message.EndsWith($"concurrency violation: saga entity Id[{generatedSagaId}] already saved."));
            }
            finally
            {
                losingSaveSession.Dispose();
            }
        }
        public async Task Save_should_fail_when_data_changes_between_read_and_update_on_same_thread()
        {
            var correlationPropertyData = Guid.NewGuid().ToString();

            var persister        = configuration.SagaStorage;
            var insertContextBag = configuration.GetContextBagForSagaStorage();

            Guid generatedSagaId;

            using (var insertSession = await configuration.SynchronizedStorage.OpenSession(insertContextBag))
            {
                var sagaData = new TestSagaData {
                    SomeId = correlationPropertyData, DateTimeProperty = DateTime.UtcNow
                };
                var correlationProperty = SetActiveSagaInstanceForSave(insertContextBag, new TestSaga(), sagaData);
                generatedSagaId = sagaData.Id;

                await persister.Save(sagaData, correlationProperty, insertSession, insertContextBag);

                await insertSession.CompleteAsync();
            }

            var startSecondTaskSync      = new TaskCompletionSource <bool>();
            var firstTaskCanCompleteSync = new TaskCompletionSource <bool>();

            var firstTask = Task.Run(async() =>
            {
                var winningContext = configuration.GetContextBagForSagaStorage();
                using (var winningSaveSession = await configuration.SynchronizedStorage.OpenSession(winningContext))
                {
                    SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(winningContext, new TestSagaData {
                        Id = generatedSagaId, SomeId = correlationPropertyData
                    });
                    var record = await persister.Get <TestSagaData>(generatedSagaId, winningSaveSession, winningContext);
                    SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(winningContext, record);

                    startSecondTaskSync.SetResult(true);
                    await firstTaskCanCompleteSync.Task;

                    record.DateTimeProperty = DateTime.UtcNow;
                    await persister.Update(record, winningSaveSession, winningContext);
                    await winningSaveSession.CompleteAsync();
                }
            });

            var secondTask = Task.Run(async() =>
            {
                await startSecondTaskSync.Task;

                var losingSaveContext = configuration.GetContextBagForSagaStorage();
                using (var losingSaveSession = await configuration.SynchronizedStorage.OpenSession(losingSaveContext))
                {
                    SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(losingSaveContext, new TestSagaData {
                        Id = generatedSagaId, SomeId = correlationPropertyData
                    });
                    var staleRecord = await persister.Get <TestSagaData>("SomeId", correlationPropertyData, losingSaveSession, losingSaveContext);
                    SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(losingSaveContext, staleRecord);

                    firstTaskCanCompleteSync.SetResult(true);
                    await firstTask;

                    staleRecord.DateTimeProperty = DateTime.UtcNow.AddHours(1);
                    await persister.Update(staleRecord, losingSaveSession, losingSaveContext);
                    Assert.That(async() => await losingSaveSession.CompleteAsync(), Throws.InstanceOf <Exception>().And.Message.EndsWith($"concurrency violation: saga entity Id[{generatedSagaId}] already saved."));
                }
            });

            await secondTask;
        }
Beispiel #3
0
        public async Task Save_should_fail_when_data_changes_between_concurrent_instances()
        {
            configuration.RequiresDtcSupport();

            var correlationPropertData = Guid.NewGuid().ToString();

            var  persister        = configuration.SagaStorage;
            var  savingContextBag = configuration.GetContextBagForSagaStorage();
            Guid generatedSagaId;

            using (var session = await configuration.SynchronizedStorage.OpenSession(savingContextBag))
            {
                var sagaData = new TestSagaData {
                    SomeId = correlationPropertData
                };
                SetActiveSagaInstanceForSave(savingContextBag, new TestSaga(), sagaData);
                generatedSagaId = sagaData.Id;

                await persister.Save(sagaData, null, session, savingContextBag);

                await session.CompleteAsync();
            }

            Assert.That(async() =>
            {
                var storageAdapter = configuration.SynchronizedStorageAdapter;
                using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
                {
                    Transaction.Current.EnlistDurable(EnlistmentWhichEnforcesDtcEscalation.Id, new EnlistmentWhichEnforcesDtcEscalation(), EnlistmentOptions.None);

                    var transportTransaction = new TransportTransaction();
                    transportTransaction.Set(Transaction.Current);

                    var unenlistedContextBag = configuration.GetContextBagForSagaStorage();
                    using (var unenlistedSession = await configuration.SynchronizedStorage.OpenSession(unenlistedContextBag))
                    {
                        var enlistedContextBag = configuration.GetContextBagForSagaStorage();
                        var enlistedSession    = await storageAdapter.TryAdapt(transportTransaction, enlistedContextBag);

                        SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(unenlistedContextBag, new TestSagaData {
                            Id = generatedSagaId, SomeId = correlationPropertData
                        });
                        var unenlistedRecord = await persister.Get <TestSagaData>(generatedSagaId, unenlistedSession, unenlistedContextBag);
                        SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(unenlistedContextBag, unenlistedRecord);

                        SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(enlistedContextBag, new TestSagaData {
                            Id = generatedSagaId, SomeId = correlationPropertData
                        });
                        var enlistedRecord = await persister.Get <TestSagaData>("Id", generatedSagaId, enlistedSession, enlistedContextBag);
                        SetActiveSagaInstanceForGet <TestSaga, TestSagaData>(enlistedContextBag, enlistedRecord);

                        await persister.Update(unenlistedRecord, unenlistedSession, unenlistedContextBag);
                        await persister.Update(enlistedRecord, enlistedSession, enlistedContextBag);

                        await unenlistedSession.CompleteAsync();
                    }

                    tx.Complete();
                }
            }, Throws.Exception.TypeOf <TransactionAbortedException>());
        }