Ideally, just wrap your transaction with a "using" statement or use a TransactionScope object, which will automatically rollback if an exception is thrown or if the transaction is not commited before it goes out of scope.
The rest of the time, as ugly as it is, I usually wrap my rollbacks in an empty try/catch block, because it is almost always in a catch handler that has a more meaningful exception. The idea is that we only want to rollback if we can, but we don't want to start throwing new exceptions if we can't (for any number unpredictable reasons), because the transaction will get rolled back as long as it's not committed anyway. You still need to try to clean up properly so it doesn't need to wait for the garbage collector, but if you can't, then the rollback isn't the real problem.
try
{
SqlTransaction trans = connection.BeginTransaction();
///blah blah blah
}
catch(Exception theExceptionICareAbout)
{
try
{
if(trans != null)
{
trans.Rollback();
}
}
catch {}
throw; //re-throws the meaningful exception.
}
Note: don't explicity re-throw the exception (i.e. "throw theExceptionICareAbout"), because that will recreate the stack trace. Instead, just use "throw" which will continue the existing exception stack.