Changes to the database structure are not transactional, so you cannot roll back the creation of a new table, for example
BS. Most DDLs are transactional and can be thrown back. Only changes that are associated with interaction with non-transactional components (for example, with the file system, for example with adding a new file to the database) cannot be undone. Any DDL that is not transactional also throws an exception very explicitly if it is called in an active transaction.
Adding and modifying tables is very explicitly transactional, and it can be easily represented using an example:
begin transaction; create table foo (a int); select * from sys.tables where object_id = object_id('foo'); rollback; select * from sys.tables where object_id = object_id('foo');
Therefore, the problem is the missing OP code, parts not placed.
System.Transactions should be used as a general comment whenever possible (given that the default constructor is corrupted ). If you use SqlConnection.BeginTransaction, it is even better to rely on IDisposable:
using (SqlTransaction trn = conn.BeginTransaction()) { ... trn.Commit (); }
System.Transactions should be encouraged, although since they are independent of code discipline, any SqlClient code in the transaction area will be automatically registered.
And btw has a raise configuration function on error, and does not return false.
And on the main real problem: how to cope with a long complex migration that cannot be registered in one transaction (for example, it simply can generate too much log). The answer is that the only possible option is to take a backup of the database at the beginning of the migration and restore from this backup if the migration fails. The alternative of providing a manual, verified, and reliable compensatory action for each migration operation to cancel the migration is incredibly complex, erroneous, and ultimately unnecessary, since restoring from a backup is much simpler and provably correct.