Este error ocurre cuando usa un bloque try/catch dentro de una transacción. Consideremos un ejemplo 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
Cuando la cuarta inserción provoca un error, el lote finaliza y la transacción se revierte. Sin sorpresas hasta el momento.
Ahora intentemos manejar ese error con un bloque 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 el error de clave duplicada, pero de lo contrario, no estamos mejor. Nuestro lote aún se cancela y nuestra transacción aún se revierte. La razón es realmente muy simple:
Los bloques TRY/CATCH no afectan las transacciones.
Debido a que XACT_ABORT está activado, en el momento en que ocurre el error de clave duplicada, la transacción está condenada. Está hecho para. Ha sido herido de muerte. Le han disparado en el corazón... y el error es el culpable. TRY/CATCH le da a SQL Server... un mal nombre. (lo siento, no pude resistir)
En otras palabras, NUNCA comprometerse y SIEMPRE retroceder. Todo lo que puede hacer un bloque TRY/CATCH es detener la caída del cadáver. Podemos usar el XACT_STATE() función para ver si nuestra transacción se puede comprometer. Si no es así, la única opción es revertir la transacción.
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
Los disparadores siempre se ejecutan dentro del contexto de una transacción, por lo que si puede evitar usar TRY/CATCH dentro de ellos, las cosas son mucho más simples.
Para una solución a su problema, un CLR Stored Proc podría volver a conectarse a SQL Server en una conexión separada para ejecutar el SQL dinámico. Obtiene la capacidad de ejecutar el código en una nueva transacción y la lógica de manejo de errores es fácil de escribir y fácil de entender en C#.