I had a write that spanned two physically separate databases. A rename that had to propagate across several tables in one database and a couple of tables in another, and the two had to stay consistent. No distributed transaction coordinator was available to me. So I did the obvious thing: opened a transaction on each, did the work, and committed them one after the other inside a try/catch with rollbacks on both sides. It felt safe. It compiled. It passed tests.

Then I drew the failure on a whiteboard, and the safety evaporated.

The window that ruins everything

Here's the structure, simplified:

await using var txA = await dbA.Database.BeginTransactionAsync();