I’ve hit a couple of surprising issues when porting code from Enterprise Services (COM+) transactions to System.Transactions.
- The loss of the transactional component “Supports”.
- Nested transactions must have exactly the same isolation level.
“Supports” says to the transaction coordinator that if there is a transaction then enlist in it, otherwise I don’t want to run a transaction at all. It looks like Microsoft have considered this obsolete since if you don’t want to run in a transaction then don’t ask for one. The problem is that I want to write a function that receives the transaction option as an argument, however the client has no way of saying “supports”. The alternatives is either “Required” or “Suppress”. If they say “Required” and one isn’t running then they’ll create a new transaction when they didn’t want one. Conversely if they pass in “Suppress” and one is running it will opt out of the transaction, again not the correct behaviour. The workaround is to write an If..condition, ok not a big problem but “Supports” was a far more elegant solution, so why remove it?
The second point is far more annoying, almost tempted to say it’s a bug. One of the fundamental ideas of transactions, since “Transaction Server” in the late 90’s, was that your code can ask for a transaction blissfully unaware of if it should be enlisted in a currently running transaction or not. This *was* a good model since it allowed you to easily use/reuse components safe in the knowledge that the Transaction Coordinator would take the strain. The only caveat to this was that you could not ask for transaction at a higher isolation level than one currently running, annoying but sensible. So what have Microsoft done now? Well when you ask for a transaction it must be exactly the same isolation level as the one running, complete madness. I’ve no idea why they’ve done this, I’m sure there is good reason and I’m awaiting a reply. In the meantime I’ve written a wrapper for System.Transaction that examines the isolation level of System.Transactions.Transaction.Current and automatically alter the isolation level passed into the wrapper to match the current value. But again, why am I having to write these workarounds?