I wrote an integration test to insert two documents with two threads simultaneously, after the test is completed, I expect none of the records inserted during the test exist in the database.
I used this trick for my integration tests(to roll back all test transactions when test finished). I want run two simultaneously task in action part of an integration test, so I wrote following test(I got the idea from this link):
[TestClass] public class MyIntegrationTest : IntegrationTestsBase { [TestMethod] public void SaveTwoDocumentsSimultaneously_WorkSuccessfully() { //Assign var doc1 = new Document() {Number = "Test1"}; var doc2 = new Document() {Number = "Test2"}; //action CountdownEvent countdown = new CountdownEvent(2); ThreadPool.QueueUserWorkItem(WorkerThread, new object[] { Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), doc1, countdown }); ThreadPool.QueueUserWorkItem(WorkerThread, new object[] { Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete), doc2, countdown }); countdown.Wait(); //assert //assertion code for chack two document inserted .... } } And this is my integration base class (Wrap each test via TransactionScope and rollback it at the end of test run):
[TestClass] public abstract class IntegrationTestsBase { private TransactionScope _scope; [TestInitialize] public void Setup() { this._scope = new TransactionScope(TransactionScopeOption.Required, new System.TimeSpan(0, 10, 0)); } [TestCleanup] public void Cleanup() { this._scope.Dispose(); } } and this is WorkerThread code(I got from this link):
private static void WorkerThread(object state) { if (state is object[] array) { var transaction = array[0]; var document = array[1] as Document; CountdownEvent countdown = array[2] as CountdownEvent; try { //Create a DependentTransaction from the object passed to the WorkerThread DependentTransaction dTx = (DependentTransaction)transaction; //Sleep for 1 second to force the worker thread to delay Thread.Sleep(1000); //Pass the DependentTransaction to the scope, so that work done in the scope becomes part of the transaction passed to the worker thread using (TransactionScope ts = new TransactionScope(dTx)) { //Perform transactional work here. using (var ctx = new PlanningDbContext()) { ctx.Documents.Add(doc); ctx.SaveChanges(); //<----exception occures here when second document insert } //Call complete on the transaction scope ts.Complete(); } //Call complete on the dependent transaction dTx.Complete(); } catch (Exception ex) { Debug.WriteLine(ex); } finally { countdown?.Signal(); } } } When I run the test I get following error at saving document point:
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Transactions.TransactionException: The operation is not valid for the state of the transaction.
Where in my code leads to this error and how can I resolve that?