public void Serializes_access_to_an_aggregate_so_that_concurrent_transactions_succeed_even_if_history_has_been_read_outside_of_modifying_transactions()
{
var user = new User();
user.Register("[email protected]", "password", Guid.NewGuid());
using (var session = OpenSession(CreateStore()))
{
session.Save(user);
user.ChangeEmail($"[email protected]");
session.SaveChanges();
}
Action updateEmail = () =>
{
using (var session = OpenSession(CreateStore()))
{
var prereadHistory = ((IEventStoreReader)session).GetHistory(user.Id);
using (var transaction = new TransactionScope())
{
var userToUpdate = session.Get<User>(user.Id);
userToUpdate.ChangeEmail($"newemail_{userToUpdate.Version}@somewhere.not");
Thread.Sleep(100);
session.SaveChanges();
transaction.Complete();
}//Sql duplicate key (AggregateId, Version) Exception would be thrown here if history was not serialized
}
};
var tasks = 1.Through(20).Select(_ => Task.Factory.StartNew(updateEmail)).ToArray();
Task.WaitAll(tasks);
using (var session = OpenSession(CreateStore()))
{
var userHistory = ((IEventStoreReader)session).GetHistory(user.Id).ToArray();//Reading the aggregate will throw an exception if the history is invalid.
userHistory.Length.Should().Be(22);//Make sure that all of the transactions completed
}
}