Questo errore si verifica quando si utilizza un blocco try/catch all'interno di una transazione. Consideriamo un esempio banale:
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 il quarto inserimento provoca un errore, il batch viene terminato e la transazione viene ripristinata. Nessuna sorpresa finora.
Ora proviamo a gestire quell'errore con un blocco 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
Abbiamo rilevato l'errore della chiave duplicata, ma per il resto non stiamo meglio. Il nostro batch viene comunque terminato e la nostra transazione viene ancora annullata. Il motivo è in realtà molto semplice:
I blocchi TRY/CATCH non influiscono sulle transazioni.
A causa dell'attivazione di XACT_ABORT, nel momento in cui si verifica l'errore della chiave duplicata, la transazione è condannata. È fatto per. È stato ferito a morte. È stato colpito al cuore... e la colpa è dell'errore. TRY/CATCH dà a SQL Server... una cattiva reputazione. (scusate, non ho resistito)
In altre parole, MAI si impegnerà e lo farà SEMPRE essere ripristinato. Tutto ciò che un blocco TRY/CATCH può fare è interrompere la caduta del cadavere. Possiamo usare XACT_STATE() funzione per vedere se la nostra transazione è vincolabile. In caso contrario, l'unica opzione è annullare la transazione.
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
I trigger vengono sempre eseguiti nel contesto di una transazione, quindi se puoi evitare di usare TRY/CATCH al loro interno, le cose sono molto più semplici.
Per una soluzione al tuo problema, un CLR Stored Proc potrebbe riconnettersi a SQL Server in una connessione separata per eseguire l'SQL dinamico. Ottieni la possibilità di eseguire il codice in una nuova transazione e la logica di gestione degli errori è sia facile da scrivere che da capire in C#.