このエラーは、トランザクション内で 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)
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
4 回目の挿入でエラーが発生すると、バッチは終了し、トランザクションはロールバックします。これまでのところ驚くことはありません。
それでは、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
重複キー エラーは検出されましたが、それ以外の場合は状況が改善されません。バッチは引き続き終了し、トランザクションはロールバックされます。理由は実に単純です:
TRY/CATCH ブロックはトランザクションに影響しません。
XACT_ABORT が ON になっているため、重複キー エラーが発生した瞬間にトランザクションが停止します。それはのために行われます。致命傷を負っています。それは心臓を撃ち抜かれました...そしてエラーのせいです. TRY/CATCH は SQL Server に名前を付けます...悪い名前です。 (すみません、我慢できませんでした)
つまり、決してしません コミットし、常に ロールバックされます。 TRY/CATCH ブロックでできることは、死体の落下を阻止することだけです。 XACT_STATE() を使用できます 関数を使用して、トランザクションがコミット可能かどうかを確認します。そうでない場合、唯一のオプションはトランザクションをロールバックすることです。
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
トリガーは常にトランザクションのコンテキスト内で実行されるため、トリガー内で TRY/CATCH の使用を避けることができれば、物事ははるかに簡単になります。
問題を解決するには、CLR Stored Proc を別の接続で SQL Server に接続して、動的 SQL を実行することができます。新しいトランザクションでコードを実行できるようになり、エラー処理ロジックは C# で記述しやすく理解しやすいものになります。