Cómo volver a generar la misma excepción en SQL Server

86

Quiero volver a generar la misma excepción en SQL Server que acaba de ocurrir en mi bloque try. Puedo lanzar el mismo mensaje pero quiero lanzar el mismo error.

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);

Esta línea mostrará un error, pero quiero una funcionalidad como esa. Esto genera un error con el número de error 50000, pero quiero que se arroje el número de error que estoy pasando @@error,

Quiero capturar este error no en la interfaz.

es decir

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

Quiero esta funcionalidad. que no se puede lograr usando raiseerror. No quiero dar un mensaje de error personalizado al final.

RAISEERROR debería devolver el error mencionado a continuación cuando paso ErrorNo para ser arrojado en la captura

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

Línea 14 Violación de la restricción UNIQUE KEY 'UK_DomainCode'. No se puede insertar una clave duplicada en el objeto 'Tags.tblDomain'. La instrucción se ha terminado.

EDITAR:

¿Cuál puede ser el inconveniente de no usar el bloque try catch si quiero que la excepción se maneje en el frontend considerando que el procedimiento almacenado contiene múltiples consultas que deben ejecutarse?

Shantanu Gupta
fuente

Respuestas:

120

Aquí hay una muestra de código limpio completamente funcional para revertir una serie de declaraciones si ocurre un error e informa el mensaje de error.

begin try
    begin transaction;

    ...

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

Antes de 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
fuente
8
Estaba usando esto en medio de un procedimiento almacenado y descubrí que continuaría ejecutándose después raiserror, que es diferente de cómo c # sale después de un throw. Así que agregué un returninterior catchporque quería igualar ese comportamiento.
Brian J
@BogdanBogdanov Retiré su edición porque el objetivo de este código es ser mínimo y no restar mérito al código real ingresado en lugar del ...
Ben Gripka
Ok, no hay problema, @Ben Gripka. Intento hacerlo más legible en la pantalla. Gracias por señalar el motivo de la reversión.
Bogdan Bogdanov
1
@BrianJ: normalmente, si la ejecución se detiene o no depende de la gravedad del error original. Si la gravedad es> = 11, la ejecución debería detenerse. Es realmente extraño, porque el raiserror dentro del bloque catch con una severidad> = 11 ya no detiene la ejecución. Su observación es muy buena y muestra cuán cerebralmente muerto está el servidor SQL, al menos 2008r2. Las versiones más nuevas parecen mejores.
costa
1
@costa Ver RAISERROR()los documentos . La severidad de ≥11 solo salta a un CATCHbloque si está dentro de un TRYbloque. Por lo tanto, debe tener BEGIN TRY…END CATCHun código alrededor si desea RAISERROR()que afecte el control de flujo.
binki
137

SQL 2012 presenta la declaración throw:

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

Si la instrucción THROW se especifica sin parámetros, debe aparecer dentro de un bloque CATCH. Esto hace que se genere la excepción detectada.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Miguel
fuente
2
Tenga cuidado, parece que esta solución solo funciona desde SQL Server 2012 y superior: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi
3
@BogdanBogdanov Para que pueda registrar el error, posiblemente manejar algunas situaciones, pero si no puede, entonces desea volver a lanzar el error para que cualquier intento / captura más alto pueda tener la oportunidad de manejarlo
Robert McKee
Sí, @Robert McKee. Me doy cuenta de eso. Lo siento, se olvidó de borrar este comentario.
Bogdan Bogdanov
3
¡Ese punto y coma en la ROLLBACKlínea es importante! Sin él, puede obtener un SQLException: Cannot roll back THROW.
idontevenseethecode
5

Relanzamiento dentro del bloque CATCH (código anterior a SQL2012, use la declaración THROW para SQL2012 y posterior):

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
fuente
4

Creo que tus opciones son:

  • No captes el error (deja que burbujee)
  • Levanta uno personalizado

En algún momento, SQL probablemente introducirá un comando reraise o la capacidad de detectar solo ciertos errores. Pero, por ahora, utilice una solución alternativa. Lo siento.

Rob Farley
fuente
7
en sql 2012 puede volver a generar una excepción usando la nueva palabra clave THROW
sergiom
5
Si. Por supuesto, eso no estaba disponible cuando se hizo esta pregunta.
Rob Farley
Sería más importante detectar y lanzar un nuevo error que no detectarlo y dejar que "burbujee" porque probablemente necesitará algunas actividades de limpieza, acción correctiva y cierre para manejar la excepción correctamente. Un ejemplo obvio sería cerrar y deshacerse de un cursor. Otros ejemplos pueden ser ejecutar un procedimiento de registro o restablecer algunos datos.
Antony Booth
1

No puede: solo el motor puede arrojar errores de menos de 50000. Todo lo que puede hacer es lanzar una excepción que se ve así ...

Mira mi respuesta aquí por favor

El interrogador aquí usó transacciones del lado del cliente para hacer lo que quería, lo que creo que es un poco tonto ...

gbn
fuente
0

Ok, esta es una solución ... :-)

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

Si observa el bloque de captura, no está generando el error sino que devuelve el número de error real (y también revertiría la transacción). Ahora, en su código .NET, en lugar de detectar la excepción, si usa ExecuteScalar (), obtiene el número de error real que desea y muestra el número apropiado.

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

Espero que esto ayude,

EDITAR: - Solo una nota, si desea obtener la cantidad de registros afectados e intentar usar ExecuteNonQuery, es posible que la solución anterior no funcione para usted. De lo contrario, creo que se adaptaría a lo que necesita. Házmelo saber.

Ashish Gupta
fuente
@Ashish Gupta: Gracias por ayuda, pero necesito que la excepción sea lanzada desde la base de datos a la interfaz, de lo contrario, tengo muchas opciones abiertas como imprimir error_number (), devolver error_number y el 1 u sugerido
Shantanu Gupta
0

La forma de detener la ejecución en un procedimiento almacenado después de que se ha producido un error y devolver el error al programa de llamada es seguir cada declaración que pueda generar un error con este código:

If @@ERROR > 0
Return

Yo mismo me sorprendí al descubrir que la ejecución en un procedimiento almacenado puede continuar después de un error, sin darme cuenta de que esto puede llevar a algunos errores difíciles de rastrear.

Este tipo de errores de manejo de paralelos (pre .Net) Visual Basic 6. Esperamos el comando Throw en SQL Server 2012.

Chuck Bevitt
fuente
0

Dado que aún no se ha mudado a 2012, una forma de implementar la propagación del código de error original es usar la parte del mensaje de texto de la excepción que está (re) lanzando desde el bloque de captura. Recuerde que puede contener alguna estructura, por ejemplo, texto XML para que el código de la persona que llama lo analice en su bloque de captura.

Yuri Makassiouk
fuente
0

También puede crear un procedimiento almacenado contenedor para esos escenarios cuando desee que la instrucción SQL se ejecute dentro de la transacción y alimente el error hasta su código.

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'
Sergey
fuente
-2

Desde el punto de vista del diseño, ¿cuál es el punto de lanzar excepciones con números de error originales y mensajes personalizados? Hasta cierto punto, rompe el contrato de interfaz entre las aplicaciones y la base de datos. Si desea detectar errores originales y manejarlos en un código superior, no los maneje en la base de datos. Luego, cuando detecta una excepción, puede cambiar el mensaje que se le presenta al usuario a lo que desee. Sin embargo, no lo haría, porque hace que el código de su base de datos hmm 'no sea correcto'. Como dijeron otros, debe definir un conjunto de sus propios códigos de error (por encima de 50000) y lanzarlos en su lugar. Luego, puede manejar los problemas de integridad ('No se permiten valores duplicados') por separado de los posibles problemas comerciales: 'El código postal no es válido', 'No se encontraron filas que coincidan con los criterios', etc.

Piotr Rodak
fuente
9
¿Cuál es el punto de lanzar excepciones con números de error originales y mensajes personalizados? Suponga que desea manejar uno o dos errores específicos (esperados) directamente en el bloque de captura y dejar el resto para las capas superiores. Por lo tanto, debe poder volver a generar las excepciones que no manejó ... preferiblemente sin tener que recurrir a informar y manejar los errores de alguna otra manera especial.
Jenda
1
Además de lo que explicó @Jenda, me gusta usar try-catch para asegurar que la ejecución del código no continúe después de una excepción, al igual que en do en C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }donde throw;solo generará la excepción original, con su contexto original.
R. Schreurs
Detecto errores y vuelvo a lanzar mensajes de error personalizados en SQL para agregar detalles que describen en qué línea ocurrió el error u otros detalles (como los datos que se intentan insertar) para ayudarme a rastrear el error más adelante.
Russell Hankins