sql - transaction - La forma "correcta" de hacer validaciones de parámetros de procedimientos almacenados
throw exception in sql server stored procedure (5)
Como puede ver en este historial de respuestas, seguí esta pregunta y acepté la respuesta, y luego procedí a ''inventar'' una solución que era básicamente la misma que su segundo enfoque.
La cafeína es mi principal fuente de energía, debido al hecho de que paso la mayor parte de mi vida medio dormida porque paso demasiado tiempo codificando; por lo tanto, no me di cuenta de mi faux-pas hasta que lo señaló correctamente.
Por lo tanto, para el registro, prefiero su segundo enfoque: usar un SP para elevar el error actual, y luego usar un TRY / CATCH alrededor de la validación de su parámetro.
Reduce la necesidad de todos los bloques IF / BEGIN / END y, por lo tanto, reduce el recuento de líneas y vuelve a centrar la validación. Al leer el código para el SP, es importante poder ver las pruebas que se realizan en los parámetros; toda la pelusa sintáctica adicional para satisfacer el analizador de SQL simplemente se interpone, en mi opinión.
Tengo un procedimiento almacenado que realiza la validación de algunos parámetros y debe fallar y detener la ejecución si el parámetro no es válido.
Mi primer enfoque para la comprobación de errores se veía así:
create proc spBaz
(
@fooInt int = 0,
@fooString varchar(10) = null,
@barInt int = 0,
@barString varchar(10) = null
)
as
begin
if (@fooInt = 0 and (@fooString is null or @fooString = ''''))
raiserror(''invalid parameter: foo'', 18, 0)
if (@barInt = 0 and (@barString is null or @barString = ''''))
raiserror(''invalid parameter: bar'', 18, 0)
print ''validation succeeded''
-- do some work
end
Esto no funcionó, ya que la gravedad 18 no detiene la ejecución y la ''validación tuvo éxito'' se imprime junto con los mensajes de error.
Sé que podría simplemente agregar una devolución después de cada raiserror pero esto se ve algo feo para mí:
if (@fooInt = 0 and (@fooString is null or @fooString = ''''))
begin
raiserror(''invalid parameter: foo'', 18, 0)
return
end
...
print ''validation succeeded''
-- do some work
Dado que los errores con una gravedad 11 y superior se detectan dentro de un bloque try / catch, otro enfoque que probé fue encapsular mi comprobación de errores dentro de dicho bloque try / catch. El problema fue que el error se tragó y no se envió al cliente en absoluto. Así que investigué y encontré una manera de rethrow a rethrow el error:
begin try
if (@fooInt = 0 and (@fooString is null or @fooString = ''''))
raiserror(''invalid parameter: foo'', 18, 0)
...
end try
begin catch
exec usp_RethrowError
return
end catch
print ''validation succeeded''
-- do some work
Todavía no estoy contento con este enfoque, así que te estoy preguntando:
¿Cómo es la validación de tu parámetro? ¿Hay algún tipo de "mejor práctica" para hacer este tipo de control?
No creo que haya una sola forma "correcta" de hacer esto.
Mi propia preferencia sería similar a su segundo ejemplo, pero con un paso de validación por separado para cada parámetro y mensajes de error más explícitos.
Como dices, es un poco engorroso y feo, pero la intención del código es obvia para cualquiera que lo lea, y hace el trabajo bien.
IF (ISNULL(@fooInt, 0) = 0)
BEGIN
RAISERROR(''Invalid parameter: @fooInt cannot be NULL or zero'', 18, 0)
RETURN
END
IF (ISNULL(@fooString, '''') = '''')
BEGIN
RAISERROR(''Invalid parameter: @fooString cannot be NULL or empty'', 18, 0)
RETURN
END
Normalmente evitamos raiseerror () y devolvemos un valor que indica un error, por ejemplo, un número negativo:
if <errorcondition>
return -1
O pase el resultado en dos parámetros de salida:
create procedure dbo.TestProc
....
@result int output,
@errormessage varchar(256) output
as
set @result = -99
set @errormessage = null
....
if <errorcondition>
begin
set @result = -1
set @errormessage = ''Condition failed''
return @result
end
Prefiero volver tan pronto como sea posible, y no veo que todo vuelva a salir desde el mismo punto al final del procedimiento. Recogí este hábito haciendo asamblea, hace años. Además, siempre devuelvo un valor:
RETURN 10
La aplicación mostrará un error fatal en números positivos y mostrará el mensaje de advertencia del usuario en valores negativos.
Siempre pasamos un parámetro OUTPUT con el texto del mensaje de error.
ejemplo:
IF ~error~
BEGIN
--if it is possible to be within a transaction, so any error logging is not ROLLBACK later
IF XACT_STATE()!=0
BEGIN
ROLLBACK
END
SET @OutputErrMsg=''your message here!!''
INSERT INTO ErrorLog (....) VALUES (.... @OutputErrMsg)
RETURN 10
END
Siempre uso el parámetro @Is_Success bit como OUTPUT. Entonces, si tengo un error, entonces @ Is_success = 0. Cuando el procedimiento principal comprueba que @ Is_Success = 0, entonces retrotrae su transacción (con transacciones secundarias) y envía un mensaje de error desde @Error_Message al cliente.