Ten błąd występuje, gdy używasz bloku try/catch w transakcji. Rozważmy trywialny przykład:
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
Gdy czwarta wstawka powoduje błąd, partia jest przerywana i transakcja jest wycofywana. Na razie żadnych niespodzianek.
Teraz spróbujmy obsłużyć ten błąd za pomocą bloku 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
Wykryliśmy błąd zduplikowanego klucza, ale poza tym nie jesteśmy w lepszej sytuacji. Nasza partia nadal jest przerywana, a nasza transakcja nadal jest wycofywana. Powód jest bardzo prosty:
Bloki TRY/CATCH nie wpływają na transakcje.
Ze względu na to, że XACT_ABORT ON, w momencie wystąpienia błędu zduplikowanego klucza, transakcja jest stracona. To jest zrobione. Został śmiertelnie ranny. Został przestrzelony w serce... i winę za ten błąd. TRY/CATCH nadaje SQL Serverowi... złą nazwę. (przepraszam, nie mogłem się oprzeć)
Innymi słowy, NIGDY zobowiąże się i ZAWSZE wycofać. Jedyne, co może zrobić blok TRY/CATCH, to powstrzymać upadek trupa. Możemy użyć XACT_STATE() funkcja, aby sprawdzić, czy nasza transakcja jest zatwierdzalna. Jeśli tak nie jest, jedyną opcją jest wycofanie transakcji.
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
Wyzwalacze zawsze działają w kontekście transakcji, więc jeśli możesz uniknąć używania w nich TRY/CATCH, wszystko jest znacznie prostsze.
Aby rozwiązać ten problem, CLR Stored Proc może połączyć się ponownie z SQL Server w oddzielnym połączeniu, aby wykonać dynamiczny SQL. Zyskujesz możliwość wykonania kodu w nowej transakcji, a logika obsługi błędów jest zarówno łatwa do napisania, jak i zrozumiała w C#.