Este erro ocorre quando você usa um bloco try/catch dentro de uma transação. Vamos considerar um exemplo trivial:
SET XACT_ABORT ON
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)
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error, XACT_ABORT kills the batch
INSERT INTO #t (i) VALUES (4)
COMMIT TRAN
SELECT * FROM #t
Quando a quarta inserção causa um erro, o lote é encerrado e a transação é revertida. Sem surpresas até agora.
Agora vamos tentar lidar com esse erro com um bloco TRY/CATCH:
SET XACT_ABORT ON
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)
BEGIN TRY
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
INSERT INTO #t (i) VALUES (4)
/* Error the Current Transaction cannot be committed and
cannot support operations that write to the log file. Roll back the transaction. */
COMMIT TRAN
SELECT * FROM #t
Detectamos o erro de chave duplicada, mas, fora isso, não estamos em melhor situação. Nosso lote ainda é encerrado e nossa transação ainda é revertida. O motivo é realmente muito simples:
Os blocos TRY/CATCH não afetam as transações.
Por ter XACT_ABORT ON, no momento em que ocorre o erro de chave duplicada, a transação está condenada. Está feito. Foi fatalmente ferido. Foi um tiro no coração... e o erro é o culpado. TRY/CATCH dá ao SQL Server... um nome ruim. (desculpe, não resisti)
Em outras palavras, ele NUNCA comprometer e irá SEMPRE ser revertido. Tudo o que um bloco TRY/CATCH pode fazer é impedir a queda do cadáver. Podemos usar o XACT_STATE() função para ver se nossa transação é confirmável. Se não for, a única opção é reverter a transação.
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 sempre executam dentro do contexto de uma transação, então se você puder evitar o uso de TRY/CATCH dentro deles, as coisas são muito mais simples.
Para uma solução para o seu problema, um CLR Stored Proc pode se conectar novamente ao SQL Server em uma conexão separada para executar o SQL dinâmico. Você ganha a capacidade de executar o código em uma nova transação e a lógica de tratamento de erros é fácil de escrever e fácil de entender em C#.