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?