1c 8 transactions provenant de sources externes. Clients ponctuels. Segmentation pour obtenir des achats répétés. Coût des marchandises dans la commande

Quelle que soit l'option de fonctionnement choisie (fichier ou client-serveur), le système 1C:Enterprise assure le travail avec les informations stockées dans la base de données à l'aide d'un mécanisme transactionnel.

Transaction- il s'agit d'une séquence indivisible d'opérations de manipulation de données du point de vue de l'impact sur la base de données. Il fonctionne sur une base tout ou rien et déplace la base de données d'un état holistique à un autre état holistique. Si, pour une raison quelconque, l'une des actions de transaction n'est pas exécutable ou si une sorte de perturbation du système se produit, la base de données revient à l'état qui était avant le début de la transaction (la transaction est annulée).

Le système 1C:Enterprise appelle implicitement des transactions lors de l'exécution de toute action liée à la modification des informations stockées dans la base de données. Par exemple, tous les gestionnaires d'événements situés dans les modules objet et recordset associés à la modification des données de la base de données sont appelés dans une transaction. La transaction lit également les objets des types suivants : Objet Planobena, Document -sujet de l'avion, ouvrage de référence, Planwid -Calculs de caractères et de mots de plan, Plan -Contre-sujet, Procédure du Conseil, Objet, Squeezers, Registre du registre, Registre -Knock -Racing, Registachgontinarinapers, Enregistrement de l'immatriculation, Re-reporting , récalcitrable.. Dans ce cas, en mode de verrouillage géré, un verrou partagé est installé par la valeur du registre pour les ensembles d'enregistrements et par les valeurs de sélection pour un ensemble d'enregistrements d'un registre d'informations indépendant.

Parallèlement à cela, le développeur peut utiliser explicitement le travail avec les transactions. Pour cela, utilisez les procédures de contexte global StartTransaction(), CommitTransaction() et CancelTransaction().

Utiliser un appel de transaction explicite

Méthode DémarrerTransaction() vous permet d'ouvrir une transaction. Toutes les modifications apportées aux informations de la base de données par des instructions ultérieures peuvent alors être entièrement acceptées ou entièrement rejetées. Pour accepter toutes les modifications apportées, utilisez la méthode ValiderTransaction(). Pour annuler toutes les modifications apportées dans une transaction ouverte, utilisez la méthode Annuler la transaction(). Si le nombre d'appels de méthode DémarrerTransaction() dépasse le nombre d'appels de méthode ValiderTransaction() ou Annuler la transaction(), alors le système effectuera un appel de méthode implicite Annuler la transaction() dans les cas suivants :

● en fin d'exécution du langage intégré (gestionnaire d'événements, connexion externe, serveur d'automatisation) ;

● lors du transfert du contrôle du serveur vers le client.

Si le nombre d'appels de méthode ValiderTransaction() ou Annuler la transaction() dépasse le nombre d'appels de méthode DémarrerTransaction(), puis lors de l'exécution d'un appel de méthode inutile ValiderTransaction() ou Annuler la transaction() une exception sera levée. Ainsi, le schéma pour travailler avec une transaction dans vue générale pourrait ressembler à ceci :

Tentative

DémarrerTransaction();

// Séquence d'instructions

CommitTransaction();

Exception

Annuler la transaction();

FinTentative ;

Lorsque vous utilisez un tel schéma, vous devez vous rappeler que toutes les erreurs qui se produisent lors de l'utilisation de la base de données ne sont pas traitées de la même manière par le système. En général, toutes les erreurs de base de données peuvent être divisées en deux catégories :

● irrécupérable,

● récupérable.

Erreurs irrécupérables- ce sont des erreurs, si elles se produisent, le fonctionnement normal du système 1C:Enterprise peut être perturbé, par exemple des données peuvent être corrompues. Si une erreur irrécupérable se produit, l'exécution du système 1C:Enterprise est dans tous les cas terminée. Si une erreur irrécupérable se produit lors de l'exécution d'une transaction, toutes les modifications apportées dans le cadre de cette transaction sont annulées par le système.

Erreurs récupérables- ce sont des erreurs qui n'entraînent pas de perturbations graves dans le fonctionnement du système 1C:Enterprise. Si une erreur récupérable se produit, le fonctionnement du système peut continuer. Dans ce cas, bien sûr, l'opération elle-même qui a provoqué l'erreur est terminée et une exception est levée, qui peut être interceptée et traitée par la construction

Tentative... Exception... FinTry.

Appel de transaction imbriquée

Au sein d'une transaction déjà en cours, vous pouvez accéder aux procédures StartTransaction(), CommitTransaction() Et Annuler la transaction(). Par exemple, le modèle d'appel suivant pourrait être utilisé :

DémarrerTransaction();

DémarrerTransaction();

CommitTransaction();

// Appel de transaction imbriquée

DémarrerTransaction();

CommitTransaction();

CommitTransaction();

Cependant, un tel appel ne signifie pas le début d’une nouvelle transaction au sein d’une transaction déjà en cours.

ATTENTION!Le système 1C:Enterprise ne prend pas en charge les transactions imbriquées.Cela signifie que seule la transaction elle-même est toujours valide. haut niveau.

Toutes les transactions appelées au sein d'une transaction déjà ouverte font en réalité partie de la même transaction, plutôt que de former une transaction imbriquée. Ainsi, l’annulation des modifications effectuées dans une transaction imbriquée n’annulera finalement pas les modifications apportées à la transaction imbriquée elle-même, mais annulera plutôt toutes les modifications apportées à la transaction de niveau supérieur. Dans le même temps, les modifications apportées à la validation dans une transaction imbriquée sont ignorées.

Impact des transactions sur le fonctionnement des objets logiciels

En général, les objets logiciels utilisés par le système 1C:Enterprise sont absolument transparents pour les transactions de base de données. En d'autres termes, les transactions de base de données peuvent être appelées lors de l'exécution diverses méthodes objets logiciels, cependant, par exemple, les actions effectuées par la base de données lors de l'annulation d'une transaction n'affectent généralement pas le correspondant logiciel objets.

Il s'ensuit que lors de l'annulation des transactions de base de données, le développeur (si nécessaire) doit assurer de manière indépendante des modifications adéquates aux données du correspondant. logiciel objets. Cela peut être fait en relisant toutes les données de l'objet ou en modifiant certains détails de l'objet du programme.

Il existe des exceptions à cette règle. En raison de la spécificité applicative importante des objets logiciels du système 1C:Enterprise, dans certains cas, l'annulation des modifications apportées à la base de données peut toujours affecter les valeurs des propriétés des objets correspondants. logiciel objets. Cela se produit dans les cas suivants :

● lorsqu'une transaction est annulée, l'attribut de comptabilisation de la pièce restaure la valeur qui était avant le début de la transaction ;

● si l'objet a été créé et écrit dans une transaction, alors lorsque la transaction est annulée, la valeur de référence est effacée ;

● si l'objet a été créé en dehors de la transaction et lors de son enregistrement dans la transaction, un code/numéro généré automatiquement a été utilisé, alors lorsque la transaction est annulée, le code/numéro est effacé.

2017-08-12

Créez des transactions personnalisées pour gérer les objets OM.

Introduction

Je pense que de nombreux consultants fonctionnels SAP ont été confrontés à la transaction de maintenance des objets de gestion organisationnelle. A savoir, la transaction PP01

L'utilisation de cette transaction offre à l'utilisateur la possibilité d'administrer les infotypes de gestion organisationnelle pour les types d'objets utilisés dans les processus métier en cours d'automatisation. Très souvent, cette transaction est utilisée comme point d'entrée unique pour travailler avec tous types d'objets de gestion organisationnelle, ce qui, en réalité, n'est pas une très bonne pratique. Eh bien, ou pas très pratique. Bien que certainement courant. Ensuite, je vais essayer de vous dire quelle alternative il pourrait y avoir.

Table T77S0, groupe "TCODE"

Lors de la configuration des objets objet OM, vous toucherez probablement le paramètre situé dans le chemin suivant dans SPRO:

IMG : Gestion du personnel -> Gestion de l'organisation -> Paramètres de base -> Amélioration du modèle de données -> Conserver les types d'objet

Ici, vous créez de nouveaux objets OM, leur trouvez des noms, sélectionnez des icônes et définissez certains paramètres pour eux... Pour le moment, nous nous intéressons au nœud " Clé de type d'objet + Transaction"

Une partie de la vue de configuration s'ouvrira devant vous T77S0 avec valeurs de groupe filtrées

Il vaut la peine de prêter attention au groupe CODE TC dans lequel, si vous regardez attentivement, vous pouvez trouver les noms techniques des transactions avec lesquelles vous avez probablement dû travailler. De plus, dans la colonne Valeur indique le type d'objet auquel une transaction particulière est destinée.

Quelle est la particularité de ces transactions ?

En utilisant des transactions conçues pour gérer un type d'objet spécifique, vous n'avez plus besoin de sélectionner ces mêmes types d'objets qui sont disponibles, par défaut, dans une transaction. PP01. C'est-à-dire en lançant, par exemple, une transaction OREN09, vous commencez immédiatement à travailler avec des objets comme L

Création d'une nouvelle transaction pour votre propre objet de gestion organisationnelle

Dans l'un de mes articles précédents, j'ai expliqué comment créer un nouvel objet OM + y ajouter une recherche structurelle.

Je ne m'éloignerai pas de ce matériel. À titre de démonstration, je vais créer une nouvelle transaction pour conserver un objet 91.

Définir un nouveau type d'objet dans T77S0

Définir le nom de la future transaction dans la vue paramétrage T77S0

La valeur "ZP91M" dans ce cas est le nom de la future transaction de maintenance de l'objet 91 . Enregistrez vos modifications.

Création d'une nouvelle transaction pour maintenir un objet OM

Par transaction SE93 créez une transaction pour conserver votre objet. Vous trouverez ci-dessous un fragment vidéo avec la séquence d'actions qui doivent être effectuées pour créer la transaction correspondante

Notez les valeurs qui ont été utilisées pour les champs Programme, Numéro d'écran,Objet d'autorisation. Maintenant, démarrez une nouvelle transaction

L'utilisateur a la possibilité de travailler uniquement avec un certain type d'objet, qui, dans un certain sens, peut être qualifié de commodité et, si vous le souhaitez, de minimiser les actions supplémentaires pour sélectionner l'objet souhaité.

La dernière fois que nous avons regardé la manière la plus simple en utilisant le langage 1C intégré. Sur la pratique transactions beaucoup plus souvent utilisé en conjonction avec la conception. Cela permet, en cas d'erreur, de continuer à exécuter le code, ainsi que de fournir un message d'erreur adéquat à l'utilisateur et d'écrire des informations dans le journal d'enregistrement ou dans un fichier journal pour une analyse ultérieure par l'administrateur système.

Si l'on se tourne vers la documentation technique ou le disque ITS, nous verrons que 1C recommande la méthode suivante pour organiser une transaction dans une tentative

Tentative //1. Début de l'opération. StartTransaction() ; //2. Un bloc d'opérations effectuées dans une transaction. //3. Si toutes les opérations réussissent, nous validons la transaction. CommitTransaction() ; Exception //4. Si des erreurs surviennent lors de l'exécution du code, annulez la transaction. Annuler la transaction() ; //5. Si nécessaire, notez-le dans le journal de bord. //6. Si nécessaire, affichez un message à l'utilisateur. FinTentative ;

En fait, le code ne nécessite aucune explication particulière. Si en cours tentatives Lors de l'exécution du code transactionnel, une erreur se produit, on tombe immédiatement dans le bloc exception, c'est à dire. avant la méthode ValiderTransaction() nous n’y arrivons tout simplement pas. Eh bien, à titre exceptionnel, nous annulons la transaction en conséquence et, si nécessaire, affichons un message d'erreur et écrivons les informations dans le journal. Il est hautement souhaitable d'enregistrer les erreurs dans le journal de bord, en particulier pour les opérations effectuées sans la participation de l'utilisateur (par exemple, les tâches de routine). Cela vous permettra d'analyser l'erreur plus tard. Au lieu de vous connecter, vous pouvez faire en sorte que les messages soient envoyés à l'administrateur par courrier électronique.

Maintenant, armés de nouvelles connaissances, essayons de modifier le code discuté dans l'article sur . Permettez-moi de vous rappeler que nous avons considéré l'entrée dans l'annuaire Marchandises et au registre d'information Prix selon le schéma suivant :

&Sur le serveur sans contexte StartTransaction() ; //enregistrer un nouveau produit Produit = Répertoires. Marchandises. CreateItem() ; Produit. Nom = "Perforation" ; Produit. Écrire() ; //notez le prix RecordSet = Registres d'informations. Prix. CreateRecordSet() ; NouvelEnregistrement = RecordSet. Ajouter() ; Nouvel enregistrement. Période = CurrentDate() ; Nouvel enregistrement. Produit = Produit. Lien; Nouvel enregistrement. Montant = 100 ; Ensemble d'enregistrements. Écrire() ; CommitTransaction() ; Fin de la procédure

Maintenant, mettons la transaction dans un bloc Tentative d'exception. Très probablement, des erreurs ne peuvent se produire que lors de l'écriture dans un répertoire ou un registre d'informations. préparation préliminaire Sortons-le de la transaction.

&Sur le serveur sans contexte Procédure RunTransactionOnServer() //créer un nouveau produit Produit = Répertoires. Marchandises. CreateItem() ; Produit. Nom = "Perforation" ; //Créer un enregistrement avec un prix RecordSet = Registres d'informations. Prix. CreateRecordSet() ; NouvelEnregistrement = RecordSet. Ajouter() ; Nouvel enregistrement. Période = CurrentDate() ; Nouvel enregistrement. Montant = 100 ; //Exécuter la transaction en tentative Tentative de StartTransaction(); Produit. Écrire() ; Nouvel enregistrement. Produit = Produit. Lien; Ensemble d'enregistrements. Écrire() ; CommitTransaction() ; Exception CancelTransaction() ; Message = Nouveau MessageVersUtilisateur ; Message. Texte = ; Message. Signaler() ; Journal d'enregistrement ( "Une erreur s'est produite lors de l'enregistrement du produit et de son prix") ; FinTentative ; Fin de la procédure

Ce qu'il ne faut pas faire

Ceux qui commencent tout juste à travailler avec des transactions ont souvent envie de le faire de cette façon

StartTransaction() ; Tentative de StartTransaction(); //Bloc d'opération CommitTransaction() ; Exception CancelTransaction() ; FinTentative ; Tentative de StartTransaction(); //Bloc d'opération CommitTransaction() ; Exception CancelTransaction() ; FinTentative ; CommitTransaction() ;

Ou en boucle

StartTransaction() ; Pour chaque tentative de boucle de données provenant d'un tableau de données pour démarrer une transaction () ; Données. Écrire() ; CommitTransaction() ; Exception CancelTransaction() ; FinTentative ; Fin du cycle ; CommitTransaction() ;

À première vue, nous avons tout fait conformément aux recommandations de la société 1C. Mais le fait est que la plateforme 1C ne prend pas en charge les transactions imbriquées. Autrement dit, d’un point de vue purement technique, il est possible d’écrire de cette façon. Mais en même temps, toutes les transactions imbriquées n’en forment pas de nouvelles, mais appartiennent à la même transaction de niveau supérieur. De cette façon, si l’une des transactions imbriquées échoue, la transaction imbriquée suivante ne peut pas être validée. Le système affichera un message du type : « Des erreurs se sont déjà produites dans cette transaction !. Montrons cela avec un exemple. Disons que nous décidons d'enregistrer deux biens, chacun dans sa propre transaction. Et faisons ces transactions imbriquées dans le troisième. Ensuite, nous provoquerons artificiellement une erreur lors de la première transaction en utilisant la méthode Lever une exception:

&Sur le serveur sans contexte Procédure RunTransactionOnServer() StartTransaction() ; Tentative de StartTransaction(); Produit = Répertoires. Marchandises. CreateItem() ; Produit. Nom = "Tableau" ; Produit. Écrire() ; Lever une exception "Erreur de saisie du produit."; CommitTransaction() ; Exception CancelTransaction() ; Message = Nouveau MessageVersUtilisateur ; Message. Texte = ErrorDescription() AttemptStartTransaction() ; Produit = Répertoires. Marchandises. CreateItem() ; Produit. Nom = "Chaise" ; Produit. Écrire() ; CommitTransaction() ; Exception CancelTransaction() ; Message = Nouveau MessageVersUtilisateur ; Message. Texte = DescriptionErreur() ; Message. Signaler() ; FinTentative ; CommitTransaction() ; Fin de la procédure

À la suite de l'exécution de cette procédure, nous verrons ce qui suit dans la fenêtre de message :

(ExternalProcessing.TransactionsAtTriing.Form.Form.Form(20)) : erreur d'écriture de l'élément. (ExternalProcessing.TransactionsAtTrying.Form.Form.Form(40)) : Erreur lors de l'appel de la méthode contextuelle (Write) : Des erreurs se sont déjà produites dans cette transaction !

Ainsi, organiser des transactions imbriquées dans 1C est absolument inutile.

Options possibles

Revenons maintenant à l'option dans laquelle nous avons enregistré le produit et son prix. Si nous avons une erreur lors de l'exécution d'une transaction, il sera difficile de comprendre à quel moment elle s'est produite - lors de l'enregistrement du produit ou lors de l'enregistrement du prix, puisque les deux se produisent au cours de la même tentative. Pour déterminer où l'erreur s'est produite, nous devons encapsuler chaque opération d'écriture dans sa propre tentative et éviter les transactions imbriquées. Pour ce faire, nous introduisons une variable booléenne Refus et en fonction de sa valeur à la fin de toutes les opérations nous validerons ou annulerons la transaction.

&Sur le serveur sans contexte Procédure RunTransactionOnServer() // Démarre la transaction Refuser = Faux ; StartTransaction() ; // Essayer d'enregistrer le produit Tentative de produit = Répertoires. Marchandises. CreateItem() ; Produit. Nom = "Perforation" ; Produit. Écrire() ; Échec de l'exception = True ; Message = Nouveau MessageVersUtilisateur ; Message. Texte = "Produit d'enregistrement d'erreur"; Message. Signaler() ; FinTentative ; // Essayer d'enregistrer le prix AttemptRecordSet = Registres d'informations. Prix. CreateRecordSet() ; NouvelEnregistrement = RecordSet. Ajouter() ; Nouvel enregistrement. Période = CurrentDate() ; Nouvel enregistrement. Produit = Produit. Lien; Nouvel enregistrement. Montant = 100 ; Ensemble d'enregistrements. Écrire() ; Échec de l'exception = True ; Message = Nouveau MessageVersUtilisateur ; Message. Texte = "Erreur lors de l'enregistrement du prix"; Message. Signaler() ; FinTentative ; // Valider ou annuler la transaction Si ce n'est pas un échec, alors CommitTransaction() ; Sinon CancelTransaction() ; Fin si ; Fin de la procédure

Nous pouvons faire la même chose lorsque nous itérons et écrivons des données dans une boucle. Dans ce cas, nous pourrons obtenir une liste de toutes les données comportant des erreurs, le cas échéant.

En préparation à la certification 1C Expert, à la veille de deux sujets très importants et mondiaux - le blocage, je voudrais examiner quelque chose sans lequel les concepts ci-dessus sont impossibles - une transaction SGBD.

Transaction- une séquence d'actions logiquement connectées et indivisibles. La transaction peut être réalisée dans son intégralité ou pas du tout. Pour valider une transaction dans le SGBD, la méthode COMMIT est utilisée.

Un exemple typique de transaction est un transfert Argent d'un compte à l'autre :

  1. démarrer une transaction ;
  2. lire le montant des fonds sur le compte numéro 123 ;
  3. réduire le solde du compte 123 de 100 roubles ;
  4. enregistrer le solde du compte numéro 123 ;
  5. lire le montant des fonds sur le compte numéro 321 ;
  6. augmentez votre solde de 100 roubles;
  7. enregistrer le nouveau montant des fonds sur le compte 321 ;
  8. valider la transaction.

Obtenez 267 leçons vidéo sur 1C gratuitement :

Comme nous pouvons le constater, si une transaction n’est pas entièrement réalisée, elle n’a aucun sens.

Exigences clés (ACID) pour un SGBD transactionnel

L'un des ensembles d'exigences les plus courants pour les transactions et les SGBD transactionnels est l'ensemble ACID (Atomicité, Cohérence, Isolation, Durabilité). Voici les propriétés que toute transaction doit avoir :

  • Atomicité— aucune transaction ne doit être enregistrée partiellement ;
  • Cohérence- le système est dans un état cohérent avant le début de la transaction et doit rester dans un état cohérent une fois la transaction terminée ;
  • Isolement— lors de l'exécution d'une transaction, les transactions parallèles ne doivent pas affecter son résultat ;
  • Durabilité- en cas d'échec, les modifications apportées par une transaction terminée avec succès doivent rester sauvegardées après la remise en service du système.

Transactions en 1C

Les transactions dans 1C 8.3 et 8.2 sont créées à la fois automatiquement et décrites par les développeurs.

Vous pouvez utiliser la méthode TransactionActive() pour savoir si une transaction est active.

Un exemple de transaction automatique consiste à traiter la publication d'un document, à écrire un élément de répertoire dans une base de données, à écrire un ensemble d'enregistrements de registre d'informations, etc.

Le titre était accrocheur, mais il débordait. Je dirai tout de suite que nous parlerons de 1C. Chers utilisateurs de 1C, vous ne savez pas comment gérer les transactions et ne comprenez pas ce que sont les exceptions. Je suis arrivé à cette conclusion en parcourant un grand nombre de Code 1C, né dans la nature de l'entreprise nationale. DANS configurations typiques tout cela est suffisant, mais une quantité épouvantable de code personnalisé est écrite de manière incompétente du point de vue de la base de données. Avez-vous déjà vu l'erreur « Cette transaction a déjà rencontré des erreurs » ? Si oui, alors le titre de l’article s’applique également à vous. Voyons enfin ce que sont les transactions et comment les gérer correctement lorsque vous travaillez avec 1C.

Pourquoi devrions-nous tirer la sonnette d’alarme ?

Tout d'abord, voyons ce qu'est l'erreur « Des erreurs se sont déjà produites dans cette transaction ». C'est en fait une chose extrêmement simple : vous essayez de travailler avec la base de données dans le cadre d'une transaction déjà annulée (annulée). Par exemple, quelque part, la méthode CancelTransaction a été appelée et vous essayez de la valider.


Pourquoi est-ce mauvais ? Parce que cette erreur ne vous dit rien sur l'endroit où le problème s'est réellement produit. Lorsque le support reçoit une capture d'écran avec un tel texte d'un utilisateur, et notamment pour code du serveur, avec lequel une personne ne travaille pas de manière interactive est... Je voulais écrire « erreur critique », mais je pensais que c'était un mot à la mode auquel personne ne fait plus attention... C'est un connard. Il s'agit d'une erreur de programmation. Ce n’est pas un problème aléatoire. Il s'agit d'un bug qui doit être corrigé immédiatement. Parce que lorsque les processus de votre serveur en arrière-plan s'arrêtent la nuit et que l'entreprise commence à perdre rapidement de l'argent, alors « Des erreurs se sont déjà produites dans cette transaction » est la dernière chose que vous souhaitez voir dans les journaux de diagnostic.


Il est bien sûr possible que le journal technologique du serveur (il est activé en production, n'est-ce pas ?) aide d'une manière ou d'une autre à diagnostiquer le problème, mais pour le moment, je ne vois pas d'option par tête - comment exactement pour y trouver la véritable cause de cette erreur. Mais la vraie raison en est une : le programmeur Vasya a reçu une exception dans une transaction et a décidé que ce n'était pas une mauvaise idée, "pensez simplement, c'est une erreur, passons à autre chose".

Que sont les transactions en 1C

Il est difficile d’écrire sur des vérités élémentaires, mais apparemment, il en faudra un peu. Les transactions dans 1C sont les mêmes que les transactions dans un SGBD. Ce ne sont pas des transactions spéciales « 1C », ce sont des transactions dans le SGBD. Selon l'idée générale des transactions, elles peuvent être exécutées entièrement ou ne pas être exécutées du tout. Toutes les modifications apportées aux tables de la base de données au cours d'une transaction peuvent être immédiatement annulées, comme si de rien n'était.


Ensuite, vous devez comprendre que 1C ne prend pas en charge les transactions imbriquées. En fait, ils ne sont pas pris en charge « dans 1C », mais pas du tout. Au moins les SGBD avec lesquels 1C peut fonctionner. Les transactions imbriquées, par exemple, n'existent pas dans MS SQL et Postgres. Chaque appel « imbriqué » à StartTransaction incrémente simplement le compteur de transactions, et chaque appel à « CommitTransaction » diminue simplement ce compteur. Ce comportement est décrit dans de nombreux livres et articles, mais les conclusions qui en découlent ne sont apparemment pas suffisamment analysées. À proprement parler, dans SQL, il existe ce qu'on appelle. SAVEPOINT, mais 1C ne les utilise pas, et cette chose est assez spécifique.



Procédure Code très utile et important (Liste des liens répertoire) StartTransaction(); Pour chaque lien de la liste des liens de répertoire Loop Directory Object = Link.GetObject(); Directory Object.SomeField = "J'ai été modifié à partir du code du programme"; Objet de répertoire.Write(); Fin du cycle ; CommitTransaction(); Fin de la procédure

Coder en anglais

Pas vraiment. Je ne veux absolument pas reproduire des exemples en anglais juste pour amuser les fans de guerres saintes et de guerres saintes.


Vous écrivez probablement du code comme celui-ci, n'est-ce pas ? L'exemple de code fourni contient des erreurs. Au moins trois. Savez-vous lesquels ? Je dirai tout de suite du premier : il est lié aux verrouillages d'objets et n'est pas directement lié aux transactions. À propos du deuxième - un peu plus tard. La troisième erreur est un blocage qui se produira lors de l'exécution parallèle de ce code, mais c'est un sujet pour un article séparé, nous ne l'examinerons pas maintenant, afin de ne pas compliquer le code. Mot-clé pour googler : serrures contrôlées par interblocage.


Veuillez noter que le code est simple. Il y a tout simplement beaucoup de cela dans vos systèmes 1C. Et il contient au moins 3 erreurs à la fois. Pensez à votre guise au nombre d'erreurs qu'il y a dans des scénarios plus complexes pour travailler avec des transactions écrites par vos programmeurs 1C :)

Verrous d'objets

Donc, la première erreur. Dans 1C, il existe des verrous d'objets, dits « optimistes » et « pessimistes ». Je ne sais pas qui a inventé le terme, je l'aurais tué :). Il est absolument impossible de se rappeler lequel d’entre eux est responsable de quoi. Ils ont été décrits en détail, ainsi que dans d'autres publications informatiques générales.


L'essence du problème est que dans l'exemple de code spécifié, un objet de base de données est modifié, mais dans une autre session, il peut y avoir un utilisateur interactif (ou un thread d'arrière-plan voisin) qui modifiera également cet objet. Ici, l'un de vous peut recevoir l'erreur « l'entrée a été modifiée ou supprimée ». Si cela se produit lors d'une session interactive, l'utilisateur se grattera les fesses, jurera et tentera de rouvrir le formulaire. Si cela se produit dans un fil de discussion en arrière-plan, vous devrez le rechercher dans les journaux. Et le journal de bord, comme vous le savez, est lent, et seules quelques personnes dans notre secteur ont mis en place la pile ELK pour les journaux 1C... (d'ailleurs, nous faisons partie de ceux qui configurent et aident les autres à configurer :) )


Bref, c’est une erreur ennuyeuse et il vaut mieux ne pas la commettre. Par conséquent, les normes de développement indiquent clairement qu'avant de modifier des objets, il est nécessaire de placer un verrou d'objet sur ceux-ci à l'aide du " Objet de répertoire.Lock()". Ensuite, la session concurrente (qui doit également faire cela) ne pourra pas démarrer l'opération de mise à jour et recevra l'échec contrôlé attendu.

Et maintenant sur les transactions

Nous avons réglé la première erreur, passons à la seconde.


Si vous ne fournissez pas de vérification des exceptions dans cette méthode, alors une exception (par exemple, très probablement dans la méthode "Write()") vous fera sortir du cette méthode sans finaliser la transaction. Une exception à la méthode « Write » peut être levée pour diverses raisons, par exemple, certaines vérifications d'application dans la logique métier sont déclenchées ou le verrouillage d'objet mentionné ci-dessus se produit. Quoi qu'il en soit, la deuxième erreur dit : Le code qui a lancé la transaction n'est pas responsable de son achèvement.



C'est exactement ce que j'appellerais ce problème. Dans notre analyseur de code statique 1C basé sur SonarQube, nous avons même intégré séparément de tels diagnostics. Maintenant, je travaille sur son développement, et l'imagination des programmeurs 1C, dont le code me vient pour analyse, me laisse parfois sous le choc et l'admiration...


Pourquoi? Car une exception levée en haut à l'intérieur d'une transaction dans 90% des cas ne permettra pas de valider cette transaction et entraînera une erreur. Il faut comprendre que 1C annule automatiquement une transaction inachevée uniquement après le retour du code de script au niveau du code de la plate-forme. Tant que vous êtes au niveau du code 1C, la transaction reste active.


Montons d'un niveau dans la pile d'appels :


Procédure ImportantCode() LinkList = GetLinkListWhere(); VeryUsefulAndImportantCode(LinkList); Fin de la procédure

Regardez ce qui se passe. Notre méthode problématique est appelée quelque part à l’extérieur, plus haut dans la pile. Au niveau de cette méthode, le développeur n'a aucune idée s'il y aura ou non des transactions à l'intérieur de la méthode Very Useful and Important Code. Et s’il y en a, seront-ils tous achevés… Nous sommes tous ici pour la paix et l’encapsulation, n’est-ce pas ? L'auteur de la méthode "ImportantCode" ne doit pas penser à ce qui se passe exactement à l'intérieur de la méthode qu'il appelle. Le même dans lequel la transaction est mal traitée. Par conséquent, une tentative d'utilisation de la base de données après qu'une exception ait été levée depuis une transaction entraînera très probablement le résultat suivant : "Dans cette transaction, bla bla..."

Répartir les transactions entre les méthodes

La deuxième règle du code « transaction-safe » : Le nombre de références de transaction au début et à la fin de la méthode doit avoir la même valeur. Vous ne pouvez pas démarrer une transaction avec une méthode et la terminer avec une autre. Il est probablement possible de trouver des exceptions à cette règle, mais il s'agira d'une sorte de code de bas niveau écrit par des personnes plus compétentes. En général, vous ne pouvez pas écrire de cette façon.


Par exemple:


Procédure ImportantCode() LinkList = GetLinkListWhere(); VeryUsefulAndImportantCode(LinkList); CommitTransaction(); // Un ticket pour l'enfer, une conversation sérieuse avec l'auteur sur nos relations de travail complexes. Fin de la procédure

Ce qui précède est un code de merde inacceptable. Vous ne pouvez pas écrire de méthodes pour que l'appelant se souvienne et garde une trace des transactions possibles (ou probables - qui sait) au sein d'autres méthodes qu'il appelle. Il s'agit d'une violation de l'encapsulation et d'une prolifération de code spaghetti qui ne peut être retracé par la raison.


Il est particulièrement amusant de se rappeler que le code réel est beaucoup plus volumineux que les exemples synthétiques de 3 lignes. Trouver des transactions de début et de fin à six niveaux d'imbrication - cela motive directement des conversations intimes avec les auteurs.

Essayer de corriger le code

Revenons à la méthode originale et essayons de la corriger. Je dirai tout de suite que nous ne corrigerons pas le verrouillage des objets pour l'instant, juste pour ne pas compliquer l'exemple de code.

La première approche d'un surnom 1C typique

En règle générale, les programmeurs 1C savent qu'une exception peut être levée lors de l'enregistrement. Ils ont également peur des exceptions, alors ils essaient de toutes les attraper. Par exemple, comme ceci :


Procédure Code très utile et important (Liste des liens répertoire) StartTransaction(); Pour chaque lien de la liste des liens de répertoire Loop Directory Object = Link.GetObject(); Directory Object.SomeField = "J'ai été modifié à partir du code du programme"; TentativeDirectoryObject.Write(); Exception Log.Error("Impossible d'écrire l'élément %1", Lien); Continuer; FinTentative ; Fin du cycle ; CommitTransaction(); Fin de la procédure

Eh bien, les choses se sont améliorées, n'est-ce pas ? Après tout, maintenant erreurs possibles les enregistrements sont traités et même enregistrés. Les exceptions ne seront plus levées lors de l'écriture d'un objet. Et dans le journal, vous pouvez même voir sur quel objet, je n'ai pas été trop paresseux et j'ai inclus un lien dans le message au lieu du laconique « Erreur lors de l'écriture d'un répertoire », comme aiment souvent l'écrire les développeurs toujours pressés. Autrement dit, il y a un souci pour l’utilisateur et une montée en compétences.


Cependant, un utilisateur expérimenté de 1C ici dira que non, cela ne s'est pas amélioré. En fait, rien n’a changé, et peut-être même s’est-il aggravé. Dans la méthode « Write() », la plateforme 1C elle-même lancera une transaction d'écriture, et cette transaction sera déjà imbriquée par rapport à la nôtre. Et si, pendant que vous travaillez avec la base de données 1C, la transaction est annulée (par exemple, une exception de logique métier est levée), alors notre transaction de niveau supérieur sera toujours marquée comme « corrompue » et ne pourra pas être validée. Par conséquent, ce code restera problématique et lorsque vous tenterez de le valider, il affichera « des erreurs se sont déjà produites ».


Imaginez maintenant que nous ne parlons pas d'une petite méthode, mais d'une pile d'appels profonde, où tout en bas quelqu'un a pris et « libéré » la transaction démarrée à partir de sa méthode. Les procédures de niveau supérieur peuvent ne pas savoir que quelqu'un là-bas a commencé des transactions. En conséquence, l’ensemble du code échoue avec une vague erreur sur laquelle il est en principe impossible d’enquêter.


Le code qui démarre une transaction est requis pour la terminer ou l'annuler. Quelles que soient les exceptions. Chaque branche de code doit être examinée pour voir si une méthode se termine sans valider ni annuler la transaction.

Méthodes de travail avec les transactions en 1C

Il ne serait pas superflu de vous rappeler ce que 1C nous propose généralement pour travailler sur les transactions. Voici les méthodes bien connues :

  • DémarrerTransaction()
  • ValiderTransaction()
  • Annuler la transaction()
  • TransactionActive()

Les 3 premières méthodes sont évidentes et font ce que leur nom indique. La dernière méthode renvoie True si le compteur de transactions est supérieur à zéro.


Et il y a une fonctionnalité intéressante. Les méthodes de sortie de transaction (Commit et Cancel) lèvent des exceptions si le nombre de transactions est nul. Autrement dit, si vous appelez l'un d'eux en dehors d'une transaction, une erreur se produira.


Comment utiliser correctement ces méthodes ? C’est très simple : il faut lire la règle formulée ci-dessus :


Comment respecter cette règle ? Essayons:


Nous avons déjà compris plus haut que la méthode Do Something est potentiellement dangereuse. Cela peut générer une sorte d’exception et la transaction « sortira » de notre méthode. Bon, ajoutons un éventuel gestionnaire d'exception :


DémarrerTransaction(); Essayez DoSomething(); Exception // mais que dois-je écrire ici ? FinTentative ; CommitTransaction();

Très bien, nous avons détecté l’erreur qui se produisait, mais que devons-nous faire pour y remédier ? Écrire un message dans le journal ? Eh bien, peut-être que si le code de journalisation des erreurs devait être exactement à ce niveau et que nous attendons une erreur ici. Et sinon? Et si nous ne nous attendions à aucune erreur ici ? Ensuite, nous devrions simplement ignorer cette exception et laisser une autre couche de l'architecture s'en occuper. Cela se fait avec l'opérateur "CauseException" sans arguments. Dans vos Javascripts, cela se fait exactement de la même manière avec l'opérateur throw.


DémarrerTransaction(); Essayez DoSomething(); Exception ThrowException ; FinTentative ; CommitTransaction();

Alors, attendez... Si nous lançons simplement l'exception plus loin, alors pourquoi une tentative est-elle nécessaire ? Voici pourquoi : la règle nous oblige à garantir la réalisation de la transaction que nous avons entamée.


DémarrerTransaction(); Essayez DoSomething(); ExceptionAnnulerTransaction(); throwException; FinTentative ; CommitTransaction();

Maintenant, ça a l'air beau. Cependant, rappelons que nous ne faisons pas confiance au code Do Something(). Et si l’auteur à l’intérieur n’avait pas lu cet article et ne savait pas comment gérer les transactions ? Et s'il l'emportait là-bas et appelait la méthode CancelTransaction ou, au contraire, la validait ? Il est très important pour nous que le gestionnaire d'exceptions n'a pas levé de nouvelle exception, sinon l'erreur d'origine sera perdue et le dépannage deviendra impossible. Et on rappelle que les méthodes Commit et Cancel peuvent lever une exception si la transaction n'existe pas. C'est là que la méthode TransactionActive s'avère utile.

Version finale

Enfin, nous pouvons écrire la version correcte et « sécurisée pour les transactions » du code. Il est la:


**UPD : les commentaires suggèrent une option plus sûre lorsque CommitTransaction est situé à l'intérieur du bloc Attempt. Cette option particulière est affichée ici ; auparavant, la fixation était située après le bloc Attempt-Exception.


DémarrerTransaction(); Essayez DoSomething(); CommitTransaction(); Exception si TransactionIsActive() Then CancelTransaction(); fin si; throwException; FinTentative ;

Attendez, mais ce n'est pas seulement « CancelTransaction » qui peut produire des erreurs. Pourquoi alors « CommitTransaction » n'est-il pas enveloppé dans la même condition que « TransactionActive » ? Encore une fois, en utilisant la même règle : Le code qui a lancé la transaction devrait être responsable de sa réalisation. Notre transaction n'est pas forcément la toute première, elle peut être imbriquée. À notre niveau d’abstraction, nous devons uniquement nous soucier de notre transaction. Tous les autres ne devraient pas nous intéresser. Ce sont des étrangers, nous ne devrions pas en être responsables. Précisément, ils NE DEVRAIENT PAS. Aucune tentative ne doit être faite pour déterminer le niveau réel du compteur de transactions. Cela brisera à nouveau l’encapsulation et conduira à « salir » la logique de gestion des transactions. Nous avons uniquement vérifié l'activité dans le gestionnaire d'exceptions et uniquement pour nous assurer que notre gestionnaire ne générera pas de nouvelle exception, "cachant" l'ancienne.

Liste de contrôle de refactorisation

Examinons certaines des situations les plus courantes nécessitant une intervention du code.


Modèle:


DémarrerTransaction(); Faire quelque chose(); CommitTransaction();

Enveloppez-le dans un design « sûr » avec une tentative, un maintien en vie et une exception.


Modèle:


Si NotTransactionActive() ThenStartTransaction()EndIf

Analyse et refactorisation. L'auteur n'a pas compris ce qu'il faisait. Il est possible de démarrer des transactions imbriquées en toute sécurité. Il n'est pas nécessaire de vérifier la condition, il vous suffit de démarrer la transaction imbriquée. En dessous du module, il y est probablement encore déformé par leur fixation. C’est garanti pour les hémorroïdes.


Une option à peu près similaire :


Si la transaction est active (), alors CommitTransaction () EndIf

de même : commettre une transaction par condition est étrange. Pourquoi y a-t-il une condition ici ? Quoi, quelqu'un d'autre aurait pu déjà enregistrer cette transaction ? Motif du procès.


Modèle:


StartTransaction() While Select.Next() Boucle // lecture d'un objet par référence // écriture d'un objet EndCycle; CommitTransaction();
  1. introduire un verrouillage contrôlé pour éviter les blocages
  2. entrez un appel à la méthode Block
  3. enveloppez "essayer" comme indiqué ci-dessus

Modèle:


StartTransaction() While Select.Next() Tentative de boucle Object.Write(); Rapport d'exception("Échec de l'écriture"); FinTentative ; Fin du cycle ; CommitTransaction();

Cette transaction ne sera plus complétée en cas d'exception. Cela ne sert à rien de continuer le cycle. Le code doit être réécrit, en vérifiant la tâche d'origine. Fournissez en outre un message d’erreur plus informatif.

Enfin

Comme vous l'avez probablement déjà deviné, je fais partie de ceux qui aiment la plate-forme 1C et son développement. Bien sûr, il y a des plaintes concernant la plate-forme, en particulier dans l'environnement Highload, mais en général, elle vous permet de développer rapidement et à moindre coût des applications d'entreprise de très haute qualité. Fournir un ORM prêt à l'emploi, une interface graphique, une interface Web, des rapports et bien plus encore. Dans les commentaires sur Habré, ils écrivent généralement toutes sortes de choses arrogantes, alors les gars - le principal problème avec 1C, en tant qu'écosystème, n'est pas une plate-forme ou un fournisseur. Il s'agit d'un seuil d'entrée trop bas, qui permet d'entrer dans l'industrie à des personnes qui ne comprennent pas ce qu'est un ordinateur, une base de données, un client-serveur, un réseau et tout le reste. 1C a rendu le développement d’applications d’entreprise trop simple. En 20 minutes, je peux écrire un système comptable pour les achats/ventes avec des rapports flexibles et un client Web. Après cela, il m’est facile de penser qu’à plus grande échelle, on peut écrire à peu près de la même manière. D'une manière ou d'une autre, 1C fera tout en interne, je ne sais pas comment, mais il le fera probablement. Laissez-moi écrire "StartTransaction()"....

Ajouter des balises