Writing a large update/delete script that doesn't provoke locks
@Jacob - I would say we've all had an unpleasant experience at some point in our lives, so don't beat yourself up.  It's admirable that you're seeking guidance and learning from it.  So hats off to you.  

Some key things to target might be the impacted rowcounts.  


If you're inserting into @idsToDelete, ideally you have a recordset around 1000 rows or less.  Higher rowcounts (thousands+) will cause your queries to slow significantly.  I've done a side by side inserting into #temptable vs. @temptable with 1000+ rows, or using a CTE and you will find time and time again that the #temptable or CTE with higher recordsets will outperform @temptable significantly.  
Deleting from a table where you are sure you're deleting more than 1 row (by ID or something), should include a WITH (ROWLOCK) hint.  This means that when scanning a table for a specified recordset to delete, it will not lock the entire tables and cause this massive blocking you're experiencing.


I suspect that a combination of the slowed performance against the table parameter with significant rowcounts, along with deleting potentially thousands of rows from each related table without that lock hint, you would find things crawl and show persistent blocking patterns.

Regarding the transaction, I would keep the transaction to deleting around the delete statements only, or consider utilizing/wrapping the tsql into a stored procedure.  It depends on the usage, but typically I recommend steering away from cursors, for performance and best practices.

I can provide an example too if that's helpful.  :)

And if you're ever still unsure, if you're able to find a second set of eyes  - that's saved a few too! 

Cheers!

-skibunnysqldiva