Programmer les contrôles d'intégrité dans les cadres de table et MultiRecords

Nous avons expliqué dans la note "Utilisation des contraintes d'intégrité Paradox" pourquoi la gestion des contraintes d'intégrité directement par paradox était à éviter. Nous allons montré ici comment programmer celles-ci dans les objets Cadres de table (que nous appelerons du diminutif TF pour TableFrame) et Multi-enregistrements (diminutif MR pour Mult-Records). Nous préconisons d'utiliser au maximum ces objets pour la mise en place de vos champs dans vos fiches car vous bénéficiez alors pleinement de la richesse du modèle évènementitel de Paradox.

 

Programmer des 'triggers'

 

 

Tout d'abord, il faut piéger les évènements susceptibles dé déclencher la vérification d'une contrainte d'intégrité. Le meilleur endroit est bien sûr la méthode intégrée 'action' des objets MR et TF. Dans cette méthode nous nous intéresserons aux actions relatives aux données (classe DataAction). En fait, l'instruction switch sur la classe d'action est destinée uniquement à organiser le code et n'est pas indispensable dans le cas présent. Nous allons ensuite piéger les différents évènements relatifs au verrouillage (qui se produit avant qu'une modification soit autorisée), au déverrouillage (lorsqu'on veut imputer (enregistrer) les modifications sur l'enregistrement, à l'insertion et à la suppression. Pour chaque évènement, nous poserons un piège (que l'on appelera trigger pour faire pro) avant que l'évènement ne se produise et un second après que l'évènement ne se soit produit.

 

Le trigger avant à pour rôle d'autoriser ou non l'opération. Le trigger après à pour rôle de compléter l'opération (qui a déjà été effectuée par Paradox). Nous donnons des noms significaitfs à nos triggers (toujours les mêmes, ça simplifie la vie) et l'on obtient le code suivant pour la méthode action des objets MR et TF:


method action(var eventInfo ActionEvent)
SWITCH
  CASE eventInfo.actionClass() = DataAction :
      SWITCH
         CASE eventInfo.id() = DataUnlockRecord or eventInfo.id() =DataPostRecord:
           IF self.Touched then
             IF tbAvantImputer_LO() = FALSE THEN
                 DISABLEDEFAULT
                eventInfo.setErrorCode(canNotDepart)
                 RETURN
              ELSE
                 DODEFAULT
                IFeventInfo.errorCode() = 0 THEN
                    tbApresImputer_LO()
                  ENDIF
              ENDIF
            ENDIF
         CASE  eventInfo.id() = DataLockRecord :
            IF tbAvantVerrouiller_LO() = FALSE THEN
                 DISABLEDEFAULT
                eventInfo.setErrorCode(userError)
             ELSE
                 DODEFAULT
                IFeventInfo.errorCode() = 0 THEN
                    tbApresVerrouiller_LO()
                 ENDIF
             ENDIF
         CASE  eventInfo.id() = DataInsertRecord :
            IF tbAvantInserer_LO() = FALSE THEN
                 DISABLEDEFAULT
                eventInfo.setErrorCode(userError)
             ELSE
                 DODEFAULT
                IFeventInfo.errorCode() = 0 THEN
                    tbApresInserer_LO()
                 ENDIF
             ENDIF
          CASE  eventInfo.id() = DataDeleteRecord :
            IF tbAvantSupprimer_LO() = FALSE THEN
                 DISABLEDEFAULT
                eventInfo.setErrorCode(userError)
             ELSE
                 DODEFAULT
                IFeventInfo.errorCode() = 0 THEN
                    tbApresSupprimer_LO()
                 ENDIF
            ENDIF
      ENDSWITCH
  CASE eventInfo.actionClass() = EditAction :
  CASE eventInfo.actionClass() = FieldAction :
  CASE eventInfo.actionClass() = MoveAction :
  CASE eventInfo.actionClass() = SelectAction :
ENDSWITCH
endmethod

Puis (ou avant) nous renseignons les méthodes personnalisées tbAvantOperation_LO et tbApresOperation_LO que nous localisons également dans les objets TF et MR (cela permettra de les programmer de façon spécifique à chaque table). Le paramètre de retour indique le résultat de l'opération. Par exemple le retour de la méthode tbAvantInserer_LO permet de vérifier si l'insertion est permise. Un retour Tue autorisera l'opération alors qu'un retour False interdit l'opération.


method tbAvantInserer_LO() LOGICAL
  Valeur retour: FALSE si erreur dans la méthode ou si insertion non autorisée
   ....... votre programmation
 &nbspRETURN TRUE
endMethod

Ainsi, on utilisera systématiquement les triggers Avant pour contrôler que l'opération est possible et les triggers Apres pour en tirer les conséquences. Il est important de bien comprendre la situation des triggers par rapport au Dodefault qui déclenche l'opération par Paradox.

 

Ainsi dans l'évènement DataInsertrecord, le Dodefault provoque l'insertion d'un enregistrement vierge (ou avec les valeurs par défaut pour la table). La programmation dans tbApresInserer_LO a pour rôle de compléter cette insertion par défaut, par exemple en affectant un numéro d'incrément à un champ (cf note Gestion des incréments) . A ce moment-là, l'utilisateur n'a pas encore effectué sa saisie.

 

En revanche, dans l'évènement DataDeleteRecord, DoDefault provoque la suppression physique de l'enregistrement. Si on veut effectuer des opérations suite à cette suppression, il est nécessaire de mémoriser les clés et autres informations qui seraient nécessaires dans tbAvantSupprimer_LO.

 

Cas particulier - L'évènement DataPostRecord:

 

L'évènement DataPostRecord est similaire au DataUnlockRecord, c'est-à-dire qu'il provoque l'enregistrement des informations, mais en revanche il ne libère pas celui-ci (l'enregistrement reste verrouillé). Un DataPostRecord est suivi tôt ou tard d'un DataUnlockRecord, sauf en cas d'incident technique indépendant de votre bonne volonté (coupure de courant par exemple).

Les cas où un DataPostRecord sont assez rares (en génral on a directement l'évènment DataUnlockrecord) se produit sont en particulier les suivants:

- Modification de la clé primaire,

- Programmation volontaire de l'évènement (par exemple par MR.action(DataPostAction)).

 

Si ces cas peuvent se produire dans votre contexte, il est nécessaire de déclencher les triggers tbAvantImputer_LO et tbApresImputer_LO également sur l'évènement DataPostRecord. Comme cet évènement sera suivi d'un DataUnlockRecord, on peut, afin d'éviter d'effectuer 2 fois les opérations conditionner l'exécution des triggers par une vérification de la propriété Touched (le Dodefault des évènements DataPostRecord et DataUnlockReocrd remet à False cette propriété). Ce test est également utile car on peut verrouiller un enregistrement sans le modifier. Dans ce cas les triggers n'ont aucune raison d'être déclenchés.

Exemples de triggers

1) Empêcher toutes les modifications

Un cas fréquent est de bloquer les mises à jour lorsq'une zone, que nous appelerons ETAT prend une certaine valeur (par exemple, on bloque la modification des commandes validées). La programmation est par exemple la suivante:


method tbAvantVerrouiller_LO() Logical
 &nbspreturn not (ETAT.Value = "V")
endmethod

2) Gérer les modifications de clé primaire

En général, on évite les modifications de clé primaire puisque celles-ci sont stockées dans les différentes tables liées pour assurer les jointures. Cependant, comme toujours, il faut pouvoir gérer les exceptions. On peut par exemple conditionner la modification par l'absence d'utilisation de cette clé dans une table et par la répercussion de la mise à jour dans une autre. Il est à noter qu'il vaut mieux appeler la méthode de contrôle dans l'évènement ChangeValue de la zone concernée plutôt que dans tbAvantImputer_LO. En effet, le changeValue va permettre de bloquer l'utilisateur au moment où il effectue sa modification alors que tbAvantImputer_LO ne le bloque que lorsqu'il veut enregistre ses modifications. Donc on mettra le contrôle dans le changeValue et la répercussion dans le trigger.

 

3) Autoriser ou non les suppressions en cascade

Dans le trigger Avant, on vérifiera que la clé primaire n'est pas référencée dans une table dont on ne veut pas autoriser la suppression en cascade (par exemple on ne supprime pas un client qui a des commandes). Dans le trigger Apres, on répercute la suppression autorisée sur les tables secondaires (on supprime toutes les adresses d'un client qui va être supprimé).

 

Pour simplifer la programmation, on peut isoler dans une méthode personnalisée, que l'on nommera par exemple IR_LO la programmation des contrôles, cette méthode étant appelée par les 2 triggers tbAvantImputer_LO et tbAvantSupprimer_LO.

 

4) Gérer les champs dupliqués:

Bien entendu, la duplication d'informations dans les tables est à proscrire (sinon vous perdez l'avantage d'avoir une base de données). Cependant, vous pouvez être amené à déroger à cette règle pour des raisons d'optimisation par exemple (la duplication peut vous permettre d'avoir des index secondaires vous permettant des accès rapides ou des présentations dans un certain ordre). Dans ce cas, il importe de ne pas oublier ni la mémorisation des informations lors de l'insertion (programmation dans le trigger tbApresInserer_LO), ni la répercussion des mises à jour des informations dupliquées, ce qui sera fait dans le trigger tbApresImputer_LO. On aura pris soin de récupérer les valeurs avant modification (par exemple dans le trigger tbAvantVerrouiller_LO) pour conditionner le traitement par un changement effectif.

Objets-type et feuilles de style

Bien sûr il est intéressant de récupérer automatiquement la programmation de la méthode Action et la pré-programmation des triggers. Pour cela il existe plusieurs possibilités dont les 2 suivantes:

- enregistrer dans une fiche modèle les objets TF et MR avec leur code. Pour insérer dans votre fiche à programmer de nouveaux objets MR et TF, vous pouvez soit copier la fiche modèle et la renommer, soit copier unitairement les objets au fur et à mesure de vos besoins.

- Faire des objets-types MR et TF qui contiendront votre programmation; Ceci s'effectue en sélectionnant l'objet concerné et en le recopiant dans la barre d'outils (option accessible via le menu surgissant sur clic droit sur l'objet). Pour conserver vos objets-types au-delà de la session courant, il est nécessaire de sauvegarder la feuille de style que vous utilisez (et qui contient les objets-type).