Cette erreur se produit lorsque vous utilisez un bloc try/catch à l'intérieur d'une transaction. Prenons un exemple 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
Lorsque la quatrième insertion provoque une erreur, le lot est terminé et la transaction est annulée. Pas de surprise jusqu'à présent.
Essayons maintenant de gérer cette erreur avec un bloc 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
Nous avons attrapé l'erreur de clé en double, mais sinon, nous ne sommes pas mieux lotis. Notre lot est toujours terminé et notre transaction est toujours annulée. La raison est en fait très simple :
Les blocs TRY/CATCH n'affectent pas les transactions.
En raison de XACT_ABORT ON, au moment où l'erreur de clé en double se produit, la transaction est vouée à l'échec. C'est fait pour. Il a été mortellement blessé. Il a été tiré dans le cœur... et l'erreur est à blâmer. TRY/CATCH donne à SQL Server... une mauvaise réputation. (désolé, je n'ai pas pu résister)
En d'autres termes, il ne sera JAMAIS s'engager et le fera TOUJOURS être annulé. Tout ce qu'un bloc TRY/CATCH peut faire, c'est interrompre la chute du cadavre. Nous pouvons utiliser le XACT_STATE() fonction pour voir si notre transaction est committable. Si ce n'est pas le cas, la seule option consiste à annuler la 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
Les déclencheurs s'exécutent toujours dans le contexte d'une transaction, donc si vous pouvez éviter d'utiliser TRY/CATCH à l'intérieur, les choses sont beaucoup plus simples.
Pour une solution à votre problème, un CLR Stored Proc peut se reconnecter à SQL Server dans une connexion distincte pour exécuter le SQL dynamique. Vous gagnez la possibilité d'exécuter le code dans une nouvelle transaction et la logique de gestion des erreurs est à la fois facile à écrire et facile à comprendre en C#.