This error occurs if you use the try / catch block inside a transaction. Consider a trivial example:
SET XACT_ABORT ON IF object_id('tempdb..#t') IS NOT NULL DROP TABLE
When the fourth insertion causes an error, the batch is aborted and the transaction is rolled back. So far no surprises.
Now try to handle this error with the TRY / CATCH block:
SET XACT_ABORT ON IF object_id('tempdb..#t') IS NOT NULL DROP TABLE
We caught a duplicate key error, but otherwise we are no better. Our party is still ending, and our transaction is still rolling back. The reason is actually very simple:
TRY / CATCH blocks do not affect transactions.
Due to the fact that XACT_ABORT ON, at the time of the occurrence of a duplicate key error, the transaction is doomed. That was done. He was mortally wounded. It pierced the heart ... and the fault is to blame. TRY / CATCH gives SQL Server ... a bad name. (sorry, could not resist)
In other words, it will NEVER commit and will <Back> ALWAYS cancel. The whole TRY / CATCH unit can do is break the body fall. We can use the XACT_STATE () function to find out if our transaction is required. If this is not the case, the only option is to roll back the transaction.
SET XACT_ABORT ON -- Try with it OFF as well. IF object_id('tempdb..#t') IS NOT NULL DROP TABLE #t CREATE TABLE #t (i INT NOT NULL PRIMARY KEY) BEGIN TRAN INSERT INTO #t (i) VALUES (1) INSERT INTO #t (i) VALUES (2) SAVE TRANSACTION Save1 BEGIN TRY INSERT INTO #t (i) VALUES (3) INSERT INTO #t (i) VALUES (1) -- dup key error END TRY BEGIN CATCH SELECT ERROR_MESSAGE() IF XACT_STATE() = -1 -- Transaction is doomed, Rollback everything. ROLLBACK TRAN IF XACT_STATE() = 1 --Transaction is commitable, we can rollback to a save point ROLLBACK TRAN Save1 END CATCH INSERT INTO #t (i) VALUES (4) IF @@TRANCOUNT > 0 COMMIT TRAN SELECT * FROM #t
Triggers are always executed in the context of a transaction, so if you can avoid using TRY / CATCH inside them, everything is much simpler.
To solve your problem, CLR Stored Proc can connect to SQL Server in a separate connection to run dynamic SQL. You get the ability to execute code in a new transaction, and the error handling logic is easy to write and easy to understand in C #.