I always wrap a transaction in a using statement.
using(IDbTransaction transaction )
{
// logic goes here.
transaction.Commit();
}
Once the transaction moves out of scope, it is disposed. If the transaction is still active, it is rolled back. This behaviour fail-safes you from accidentally locking out the database. Even if an unhandled exception is thrown, the transaction will still rollback.
In my code I actually omit explicit rollbacks and rely on the using statement to do the work for me. I only explicitly perform commits.
I've found this pattern has drastically reduced record locking issues.