TransactionScope, while conceptually simple, hides a lot of magic behind the scenes. It enables you to use one transaction that's distributed over several machines. To do so, it needs complex support from the database - and the overhead may or may not be high.
A simpler more limited piece of technology, may suffice for you: DbTransaction. Your Db provider can implement a transaction with semantics similar to sql's BEGIN TRANSACTION
and COMMIT TRANSACTION
: in short, a transaction that spans only a single connection on a single machine.
Your code would then look something like...
DbCommand cmd = ...;
using (DbTransaction trans = Connection.BeginTransaction()) {
cmd.Transaction = trans; //sometimes optional, though MS-SQL requires it.
cmd.ExecuteNonQuery()
[...other db commands with the same connection and transaction...]
trans.Commit();
}
To be crystal clear: you can use several other connections while the transaction is open - but there's no link between them. Commands executed on different connections aren't rolled back on transaction abort (much like arbitrary C# code won't be rolled back on transaction abort).
This approach is lighter weight, more finely grained, and works for a broader range of database providers, and it doesn't require the MSDTC to boot. On the flip side, transactions are connection-specific (if you have several connections, they don't share the transaction), they can't be distributed, and you'll need to manually enlist commands into the transaction (at least for MS-SQL).
Note, if you fail to enlist a command in a transaction, MS-SQL will throw an exception, SQLite will implicitly enroll the command nevertheless, and I'm not sure what oracle does. Since all commands must be enrolled in the transactions anyhow, this is just an unhandily redundant API, but it's not terribly problematic.