Jak ponownie zgłosić ten sam wyjątek w programie SQL Server

86

Chcę ponownie zgłosić ten sam wyjątek w SQL Server, który właśnie wystąpił w moim bloku try. Mogę wysłać tę samą wiadomość, ale chcę zgłosić ten sam błąd.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

Ta linia pokaże błąd, ale chcę mieć taką funkcjonalność. Rodzi to błąd z numerem błędu 50000, ale chcę, aby był wyrzucany numer błędu, który mijam @@error,

Chcę uchwycić ten błąd nie na frontendzie.

to znaczy

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

Chcę mieć tę funkcjonalność. czego nie można osiągnąć za pomocą raiseerror. Nie chcę wyświetlać niestandardowego komunikatu o błędzie na zapleczu.

RAISEERROR Powinien zwrócić poniżej wspomniany błąd, gdy przekażę ErrorNo do wrzucenia do połowu

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Wiersz 14 Naruszenie ograniczenia UNIQUE KEY „UK_DomainCode”. Nie można wstawić zduplikowanego klucza w obiekcie „Tags.tblDomain”. Oświadczenie zostało zakończone.

EDYTOWAĆ:

Jaka może być wada nieużywania bloku try catch, jeśli chcę, aby wyjątek był obsługiwany na interfejsie użytkownika, biorąc pod uwagę, że procedura składowana zawiera wiele zapytań, które należy wykonać?

Shantanu Gupta
źródło

Odpowiedzi:

120

Oto w pełni funkcjonalny, czysty przykład kodu, który umożliwia wycofanie serii instrukcji w przypadku wystąpienia błędu i zgłoszenie komunikatu o błędzie.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

Przed SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch
Ben Gripka
źródło
8
Używałem tego w środku procedury składowanej i stwierdziłem, że będzie on nadal wykonywany po raiserror, co różni się od sposobu, w jaki C # kończy działanie po pliku throw. Więc dodałem returnwewnątrz, catchponieważ chciałem dopasować to zachowanie.
Brian J,
@BogdanBogdanov Wycofałem twoją edycję, ponieważ chodzi o to, aby ten kod był minimalny i nie umniejszał rzeczywistego kodu wprowadzonego w miejsce ...
Ben Gripka
Ok, nie ma problemu, @Ben Gripka. Staram się, aby był bardziej czytelny na ekranie. Dziękujemy za wskazanie powodu wycofania zmian.
Bogdan Bogdanov
1
@BrianJ: zwykle to, czy wykonanie zostanie zatrzymane, czy nie, zależy od wagi pierwotnego błędu. Jeśli istotność jest> = 11, wykonywanie powinno zostać zatrzymane. To naprawdę dziwne, ponieważ błąd raiserror wewnątrz bloku catch z istotnością> = 11 nie zatrzymuje już wykonania. Twoja obserwacja jest bardzo dobra i pokazuje, jak martwy mózg jest serwer SQL, co najmniej 2008r2. Nowsze wersje wydają się lepsze.
costa
1
@costa See RAISERROR()'s docs . Poziom istotności ≥11 przeskakuje do CATCHbloku tylko wtedy, gdy znajduje się wewnątrz TRYbloku. Więc musisz mieć BEGIN TRY…END CATCHwokół kodu, jeśli chcesz RAISERROR()mieć wpływ na kontrolę przepływu.
binki
137

SQL 2012 wprowadza instrukcję throw:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

Jeśli instrukcja THROW jest określona bez parametrów, musi pojawić się wewnątrz bloku CATCH. Powoduje to zgłoszenie przechwyconego wyjątku.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Michał
źródło
2
Uwaga, wygląda na to, że to rozwiązanie działa tylko od serwera sql 2012 i nowszych: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi
3
@BogdanBogdanov Abyś mógł zarejestrować błąd, prawdopodobnie poradzisz sobie z niektórymi sytuacjami, ale jeśli nie możesz, chcesz ponownie zgłosić błąd, aby każda wyższa próba / złapanie miała szansę sobie z tym poradzić
Robert McKee
Tak, @Robert McKee. Rozumiem to. Przepraszamy, że zapomniałem wyczyścić ten komentarz.
Bogdan Bogdanov
3
Ten średnik w ROLLBACKwierszu jest ważny! Bez tego możesz uzyskać SQLException: Cannot roll back THROW.
idontevenseethecode
5

Ponowne wczytywanie wewnątrz bloku CATCH (kod przed SQL2012, użyj instrukcji THROW dla SQL2012 i nowszych):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)
nzeemin
źródło
4

Myślę, że masz następujące możliwości:

  • Nie łap błędu (pozwól mu wybuchnąć)
  • Podnieś niestandardowy

W pewnym momencie SQL prawdopodobnie wprowadzi polecenie reraise lub możliwość wychwycenia tylko niektórych błędów. Ale na razie użyj obejścia. Przepraszam.

Rob Farley
źródło
7
w sql 2012 możesz ponownie zgłosić wyjątek za pomocą nowego słowa kluczowego THROW
sergiom
5
Tak. Oczywiście nie było to dostępne, gdy zadano to pytanie.
Rob Farley
Ważniejsze byłoby wyłapanie i wyrzucenie nowego błędu, niż nie wychwycenie go i pozwolenie na jego „wypalenie”, ponieważ prawdopodobnie będziesz potrzebować pewnych czynności porządkujących, korygujących i zamykających, aby poprawnie obsłużyć wyjątek. Oczywistym przykładem byłoby zamknięcie i pozbycie się kursora. Innymi przykładami może być wykonanie procedury logowania lub zresetowanie niektórych danych.
Antony Booth,
1

Nie możesz: tylko silnik może zgłaszać błędy mniejsze niż 50000. Wszystko, co możesz zrobić, to zgłosić wyjątek, który wygląda tak ...

Zobacz moją odpowiedź tutaj, proszę

Pytający tutaj wykorzystał transakcje po stronie klienta, aby zrobić to, czego chciał, co moim zdaniem jest trochę głupie ...

gbn
źródło
0

Ok, to obejście ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

Jeśli zauważysz blok catch, nie powoduje on błędu, ale zwraca rzeczywisty numer błędu (a także wycofa transakcję). Teraz w swoim kodzie .NET, zamiast wychwytywania wyjątku, jeśli używasz ExecuteScalar (), otrzymasz rzeczywisty numer błędu, który chcesz i wyświetlisz odpowiednią liczbę.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Mam nadzieję że to pomoże,

EDYCJA: - Tylko uwaga, jeśli chcesz uzyskać liczbę rekordów, na które ma to wpływ i próbujesz użyć funkcji ExecuteNonQuery, powyższe rozwiązanie może nie działać dla Ciebie. W przeciwnym razie myślę, że będzie pasował do tego, czego potrzebujesz. Daj mi znać.

Ashish Gupta
źródło
@Ashish Gupta: Thx za pomoc, ale wyjątek muszą być wyrzucane z bazy danych do frontend, inaczej mam wiele opcji otwierania jak numer_błędu print (), zwrot numer_błędu i 1 U zasugerował
Shantanu Gupta
0

Sposobem na zatrzymanie wykonywania w procedurze składowanej po wystąpieniu błędu i przeniesienie błędu z powrotem do programu wywołującego jest wykonanie każdej instrukcji, która może zgłosić błąd z tym kodem:

If @@ERROR > 0
Return

Sam byłem zaskoczony, gdy dowiedziałem się, że wykonywanie w procedurze składowanej może być kontynuowane po błędzie - nieświadomość tego może prowadzić do trudnych do wyśledzenia błędów.

Ten typ obsługi błędów równoległych (pre .Net) Visual Basic 6. Czekamy na polecenie Throw w SQL Server 2012.

Chuck Bevitt
źródło
0

Biorąc pod uwagę, że nie przeniosłeś się jeszcze do 2012, jednym ze sposobów zaimplementowania propagacji oryginalnego kodu błędu jest użycie części wiadomości tekstowej wyjątku, który (ponownie) wyrzucasz z bloku catch. Pamiętaj, że może zawierać pewną strukturę, na przykład tekst XML dla kodu wywołującego do przeanalizowania w swoim bloku catch.

Yuri Makassiouk
źródło
0

Możesz również utworzyć opakowującą procedurę składowaną dla tych scenariuszy, jeśli chcesz, aby instrukcja SQL była wykonywana w ramach transakcji i przekazywała błąd do kodu.

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'
Siergiej
źródło
-2

Z punktu widzenia projektowania, jaki jest sens rzucania wyjątków z oryginalnymi numerami błędów i niestandardowymi komunikatami? Do pewnego stopnia zrywa kontrakt interfejsu między aplikacjami a bazą danych. Jeśli chcesz wyłapać oryginalne błędy i obsłużyć je w wyższym kodzie, nie obsługuj ich w bazie danych. Następnie, gdy złapiesz wyjątek, możesz zmienić wiadomość prezentowaną użytkownikowi na dowolną. Nie zrobiłbym tego jednak, ponieważ powoduje to, że kod twojej bazy danych hmm jest „nieprawidłowy”. Jak powiedzieli inni, powinieneś zdefiniować zestaw własnych kodów błędów (powyżej 50000) i zamiast tego je wyrzucić. Następnie możesz rozwiązać problemy z integralnością („Powielone wartości są niedozwolone”) niezależnie od potencjalnych problemów biznesowych - „Kod pocztowy jest nieprawidłowy”, „Nie znaleziono wierszy spełniających kryteria” itd.

Piotr Rodak
źródło
9
Po co rzucać wyjątki z oryginalnymi numerami błędów i niestandardowymi komunikatami? Załóżmy, że chcesz obsłużyć jeden lub dwa określone (oczekiwane) błędy bezpośrednio w bloku catch, a resztę pozostawić dla wyższych warstw. Dlatego musisz mieć możliwość ponownego wyrzucenia wyjątków, których nie obsługiwałeś ... najlepiej bez konieczności zgłaszania i obsługi błędów w inny, specjalny sposób.
Jenda
1
Oprócz tego, co wyjaśnił @Jenda, lubię używać try-catch, aby zapewnić, że wykonanie kodu nie będzie kontynuowane po wyjątku, podobnie jak w C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }gdzie throw;po prostu podniesie oryginalny wyjątek z jego oryginalnym kontekstem.
R. Schreurs,
Wyłapuję błędy i ponownie wysyłam niestandardowe komunikaty o błędach w języku SQL, aby dodać szczegóły opisujące, w którym wierszu wystąpił błąd lub inne szczegóły (takie jak dane, które próbują wstawić), aby pomóc mi później wyśledzić błąd.
Russell Hankins