web-dev-qa-db-fra.com

La "bonne" façon de faire la validation des paramètres de procédure stockée

J'ai une procédure stockée qui effectue une certaine validation des paramètres et devrait échouer et arrêter l'exécution si le paramètre n'est pas valide.

Ma première approche pour la vérification des erreurs ressemblait à ceci:

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

Cela n'a pas fait l'affaire car la gravité 18 n'arrête pas l'exécution et la "validation réussie" est imprimée avec les messages d'erreur.

Je sais que je pourrais simplement ajouter un retour après chaque relance, mais cela me semble plutôt laid:

  if (@fooInt = 0 and (@fooString is null or @fooString = ''))
  begin
    raiserror('invalid parameter: foo', 18, 0)
    return
  end

  ...

  print 'validation succeeded'
  -- do some work

Étant donné que les erreurs de gravité 11 et plus sont détectées dans un bloc try/catch, une autre approche que j'ai testée consistait à encapsuler ma vérification d'erreur dans un tel bloc try/catch. Le problème était que l'erreur a été avalée et n'a pas du tout été envoyée au client. J'ai donc fait quelques recherches et trouvé un moyen de rethrow l'erreur:

  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

Je ne suis toujours pas satisfait de cette approche, je vous demande donc:

À quoi ressemble la validation de vos paramètres? Existe-t-il une sorte de "meilleure pratique" pour effectuer ce type de vérification?

34
VVS

Je ne pense pas qu'il existe une seule "bonne" façon de procéder.

Ma propre préférence serait similaire à votre deuxième exemple, mais avec une étape de validation distincte pour chaque paramètre et des messages d'erreur plus explicites.

Comme vous le dites, c'est un peu lourd et laid, mais l'intention du code est évidente pour quiconque le lit, et il fait le travail.

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
48
LukeH

Comme vous pouvez le voir dans cet historique de réponses, j'ai suivi cette question et accepté la réponse, puis j'ai `` inventé '' une solution qui était fondamentalement la même que votre deuxième approche.

La caféine est ma principale source d'énergie, car je passe la majeure partie de ma vie à moitié endormi, car je passe beaucoup trop de temps à coder; ainsi je n'ai pas réalisé mon faux pas tant que vous ne l'avez pas signalé à juste titre.

Par conséquent, pour mémoire, je préfère votre deuxième approche: utiliser un SP pour augmenter l'erreur actuelle, puis utiliser un TRY/CATCH autour de la validation de votre paramètre.

Il réduit le besoin de tous les blocs IF/BEGIN/END et réduit donc le nombre de lignes ainsi que recentre l'attention sur la validation. Lors de la lecture du code pour le SP il est important de pouvoir voir les tests effectués sur les paramètres; tout le fluff syntaxique supplémentaire pour satisfaire l'analyseur SQL se met en travers, dans mon opinion.

1
Andras Zoltan

Nous évitons normalement raiseerror () et retournons une valeur qui indique une erreur, par exemple un nombre négatif:

if <errorcondition>
    return -1

Ou passez le résultat dans deux paramètres:

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
1
Andomar

J'utilise toujours le paramètre @Is_Success bit comme OUTPUT. Donc, si j'ai une erreur, alors @ Is_success = 0. Lorsque la procédure parent vérifie que @ Is_Success = 0, elle annule sa transaction (avec les transactions enfants) et envoie un message d'erreur de @Error_Message au client.

0
Dalex

Je préfère revenir le plus tôt possible, et ne vois pas l'intérêt de tout faire revenir du même point à la fin de la procédure. J'ai repris cette habitude en faisant l'assemblage, il y a des années. De plus, je retourne toujours une valeur:

RETURN 10

L'application affichera une erreur fatale sur les nombres positifs et affichera le message d'avertissement de l'utilisateur sur les valeurs négatives.

Nous renvoyons toujours un paramètre OUTPUT avec le texte du message d'erreur.

exemple:

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
0
KM.