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; }
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>()); }