Rollbacks are tough - AFAIK, there's really only 2 ways to go about it. Either a 2 phase commit protocol, or compensating transactions. You really have to find a way to structure your tasks in one of these fashions.
Usually, the better idea is to take advantage of other folks' hard work and use technologies that already have 2PC or compensation built in. That's one reason that RDBMS are so popular.
So, the specifics are task dependent...but the pattern is fairly easy:
class Compensator {
Action Action { get; set; }
Action Compensate { get; set; }
}
Queue<Compensator> actions = new Queue<Compensator>(new Compensator[] {
new Compensator(SendEmail, UndoSendEmail),
new Compensator(ArchiveReportsInDatabase, UndoArchiveReportsInDatabase),
new Compensator(CreateAFile, UndoCreateAFile)
});
Queue<Compensator> doneActions = new Queue<Compensator>();
while (var c = actions.Dequeue() != null) {
try {
c.Action();
doneActions.Add(c);
} catch {
try {
doneActions.Each(d => d.Compensate());
} catch (EXception ex) {
throw new OhCrapException("Couldn't rollback", doneActions, ex);
}
throw;
}
}
Of course, for your specific tasks - you may be in luck.
- Obviously, the RDBMS work can already be wrapped in a transaction.
- If you're on Vista or Server 2008, then you get Transactional NTFS to cover your CreateFile scenario.
- Email is a bit trickier - I don't know of any 2PC or Compensators around it (I'd only be slightly surprised if someone pointed out that Exchange has one, though) so I'd probably use MSMQ to write a notification and let a subscriber pick it up and eventually email it. At that point, your transaction really covers just sending the message to the queue, but that's probably good enough.
All of these can participate in a System.Transactions Transaction, so you should be in pretty good shape.