1c 8 transactions from external sources. One-time clients. Segmentation to get repeat purchases. Cost of goods in the order

Regardless of the chosen operating option (file or client-server), the 1C:Enterprise system ensures work with information stored in the database using a transaction mechanism.

Transaction- this is an indivisible sequence of data manipulation operations from the point of view of impact on the database. It operates on an all-or-nothing basis and moves the database from one holistic state to another holistic state. If for some reason one of the transaction actions is not executable or some kind of system disruption occurs, the database returns to the state that was before the transaction began (the transaction is rolled back).

The 1C:Enterprise system implicitly calls transactions when performing any actions related to modifying information stored in the database. For example, all event handlers located in object and recordset modules associated with modification of database data are called in a transaction. The transaction also reads objects of the following types: Planobena Object, Document -aircraft subject, reference book, Planwid -Charactermentary and Planword Calculations, Plan -Counter -subject, Board Procedure, Object, Squeezers, Register of Register, Register -Knock -Racing, Registachgontinarinapers, Registration of Registration, Re -reporting, recalcitable. Records. In this case, in the managed locking mode, a shared lock is installed by the value of the register for sets of records and by the selection values ​​for a set of records of an independent information register.

Along with this, the developer can use explicitly working with transactions. To do this, use the global context procedures StartTransaction(), CommitTransaction() and CancelTransaction().

Using an Explicit Transaction Call

Method StartTransaction() allows you to open a transaction. All changes to database information made by subsequent statements can then be either entirely accepted or rejected entirely. To accept all changes made, use the method CommitTransaction(). To undo all changes made in an open transaction, use the method CancelTransaction(). If the number of method calls StartTransaction() exceeds the number of method calls CommitTransaction() or CancelTransaction(), then the system will make an implicit method call CancelTransaction() in the following cases:

● at the end of execution of the built-in language (event handler, external connection, automation server);

● when transferring control from the server to the client.

If the number of method calls CommitTransaction() or CancelTransaction() exceeds the number of method calls StartTransaction(), then when performing an unnecessary method call CommitTransaction() or CancelTransaction() an exception will be thrown. Thus, the scheme for working with a transaction in general view might look like this:

Attempt

StartTransaction();

// Sequence of statements

CommitTransaction();

Exception

CancelTransaction();

EndAttempt;

When using such a scheme, you should remember that not all errors that occur while working with the database are processed by the system in the same way. In general, all database errors can be divided into two categories:

● unrecoverable,

● recoverable.

Unrecoverable errors- these are errors, if they occur, the normal functioning of the 1C:Enterprise system may be disrupted, for example, data may be corrupted. If an unrecoverable error occurs, the execution of the 1C:Enterprise system is terminated in any case. If an unrecoverable error occurs during the execution of a transaction, then all changes made as part of that transaction are canceled by the system.

Recoverable errors- these are errors that do not cause serious disruptions in the operation of the 1C:Enterprise system. If a recoverable error occurs, further operation of the system can continue. In this case, naturally, the operation itself that caused the error is terminated, and an exception is raised, which can be intercepted and processed by the construct

Attempt... Exception... EndTry.

Nested transaction call

Within a transaction already in progress, you can access procedures StartTransaction(), CommitTransaction() And CancelTransaction(). For example, the following call pattern could be used:

StartTransaction();

StartTransaction();

CommitTransaction();

// Nested transaction call

StartTransaction();

CommitTransaction();

CommitTransaction();

However, such a call does not mean the start of a new transaction within the framework of an already running one.

ATTENTION!The 1C:Enterprise system does not support nested transactions.This means that only the transaction itself is always valid. top level.

All transactions called within an already open transaction are actually part of the same transaction, rather than forming a nested transaction. Thus, undoing changes performed in a nested transaction will ultimately not undo the changes in the nested transaction itself, but rather undo all changes in the top-level transaction. At the same time, commit changes made in a nested transaction are ignored.

Impact of transactions on the operation of software objects

In general, software objects used by the 1C:Enterprise system are absolutely transparent to database transactions. In other words, database transactions can be called when executing various methods software objects, however, for example, the actions performed by the database when rolling back a transaction generally do not affect the corresponding software objects.

It follows from this that when canceling database transactions, the developer (if necessary) must independently ensure adequate changes to the data of the corresponding software objects. This can be done by re-reading all the object data or by changing some of the program object details.

There are exceptions to this rule. Due to the significant application specificity of software objects of the 1C:Enterprise system, in some cases, rolling back changes made in the database can still affect the values ​​of the properties of the corresponding software objects. This happens in the following cases:

● when a transaction is cancelled, the document posting attribute restores the value that was before the start of the transaction;

● if the object was created and written in a transaction, then when the transaction is rolled back, the reference value is cleared;

● if the object was created outside of the transaction and when recording it in the transaction, an automatically generated code/number was used, then when the transaction is cancelled, the code/number is cleared.

2017-08-12

Create custom transactions for maintaining OM objects.

Introduction

I think that many functional SAP consultants have encountered the transaction of maintaining organizational management objects. Namely, the transaction PP01

Using this transaction provides the user with the ability to administer organizational management infotypes for those object types that are used in the business processes being automated. Very often this transaction is used as a single entry point for working with all types of organizational management objects, which, in reality, is not a very good practice. Well, or not very convenient. Although certainly common. Next, I will try to tell you what an alternative there might be.

Table T77S0, group "TCODE"

When setting up OM object objects, you will probably touch the setting located in the following path in SPRO:

IMG: Personnel Management -> Organizational Management -> Basic Settings -> Data Model Enhancement -> Maintain Object Types

Here you create new OM objects, come up with names for them, select icons and define some settings for them... At the moment we are interested in the node " Object Type Key + Transaction"

Part of the setup view will open in front of you T77S0 with filtered group values

Worth paying attention to the group TCODE in which, if you look closely, you can find the technical names of transactions that you most likely had to work with. Moreover, in the column Value indicates the type of object for which a particular transaction is intended.

What is special about these transactions?

By using transactions that are designed to maintain a specific type of object, you no longer need to select these very types of objects that are available, by default, in a transaction PP01. That is, by launching, for example, a transaction PO09, you immediately start working with objects like L

Creating a new transaction for your own organizational management object

In one of my previous posts, I talked about how you can create a new OM object + add a structural search for it

I won’t go far from this material. As a demonstration, I'll create a new transaction to maintain an object 91.

Defining a new object type in T77S0

Define the name of the future transaction in the setup view T77S0

The value "ZP91M" in this case is the name of the future transaction for maintaining the object 91 . Save your changes.

Creating a new transaction to maintain an OM object

Via transaction SE93 create a transaction to maintain your object. Below is a video fragment with the sequence of actions that must be performed to create the corresponding transaction

Note the values ​​that were used for the fields Program, Screen Number,Authorization Object. Now start a new transaction

The user has the opportunity to work only with a certain type of object, which, in a certain sense, can be called convenience, and, if you like, minimizing additional actions to select the desired object.

Last time we looked at the simplest way using the built-in 1C language. On practice transactions much more often used in conjunction with the design. This allows, in the event of an error, to continue executing the code, as well as provide an adequate error message to the user and write information to the registration log or to a log file for subsequent analysis by the system administrator.

If we turn to the technical documentation or the ITS disk, we will see that 1C recommends the following method of organizing a transaction in an attempt

Attempt //1. Start of transaction. StartTransaction() ; //2. A block of operations performed in a transaction. //3. If all operations are successful, we commit the transaction. CommitTransaction() ; Exception //4. If errors occur while executing the code, cancel the transaction. CancelTransaction() ; //5. If necessary, record in the log book. //6. If necessary, display a message to the user. EndAttempt ;

Actually, the code does not require any special explanation. If in progress attempts When executing transactional code, an error occurs, we immediately fall into the block exception, i.e. before method CommitTransaction() we just don't get there. Well, in the exception, we cancel the transaction accordingly and, if necessary, display an error message and write the information to the log. It is highly desirable to record errors in the log book, especially for those operations that are performed without user participation (for example, routine tasks). This will allow you to analyze the error later. Instead of logging, you can arrange for messages to be sent to the administrator by email.

Now, armed with new knowledge, let's try to modify the code discussed in the article about . Let me remind you that we considered the entry in the directory Goods and to the information register Price according to the following scheme:

&OnServerWithout Context StartTransaction() ; //record a new product Product = Directories. Goods. CreateItem() ; Product. Name = "Hole Punch" ; Product. Write() ; //write down the price RecordSet = InformationRegisters. Price. CreateRecordSet() ; NewRecord = RecordSet. Add() ; NewRecord. Period = CurrentDate() ; NewRecord. Product = Product. Link; NewRecord. Amount = 100 ; RecordSet. Write() ; CommitTransaction() ; End of Procedure

Now let's put the transaction in a block Attempt Exception. Most likely, errors can only occur when writing to a directory or information register, so preliminary preparation Let's take it outside the transaction.

&OnServerWithout Context Procedure RunTransactionOnServer() //create a new product Product = Directories. Goods. CreateItem() ; Product. Name = "Hole Punch" ; //Create a record with a price RecordSet = InformationRegisters. Price. CreateRecordSet() ; NewRecord = RecordSet. Add() ; NewRecord. Period = CurrentDate() ; NewRecord. Amount = 100 ; //Execute the transaction in attempt Attempt to StartTransaction(); Product. Write() ; NewRecord. Product = Product. Link; RecordSet. Write() ; CommitTransaction() ; Exception CancelTransaction() ; Message = New MessageToUser; Message. Text = ; Message. To report() ; LogRegistration( "An error occurred while recording the product and its price") ; EndAttempt ; End of Procedure

What NOT to do

Those who are just starting to work with transactions often have a desire to do it this way

StartTransaction() ; Attempt to StartTransaction(); //Operation block CommitTransaction() ; Exception CancelTransaction() ; EndAttempt ; Attempt to StartTransaction(); //Operation block CommitTransaction() ; Exception CancelTransaction() ; EndAttempt ; CommitTransaction() ;

Or in a loop

StartTransaction() ; For each Data From Data Array Loop Attempt to Start Transaction() ; Data. Write() ; CommitTransaction() ; Exception CancelTransaction() ; EndAttempt ; EndCycle ; CommitTransaction() ;

At first glance, we did everything in accordance with the recommendations of the 1C company. But the fact is that the 1C platform does not support nested transactions. That is, purely technically, it is possible to write this way. But at the same time, all nested transactions do not form new ones, but belong to the same top-level transaction. This way, if one of the nested transactions fails, the next nested transaction cannot be committed. The system will display a message like: “Errors have already occurred in this transaction!”. Let's demonstrate this with an example. Let's say we decide to record two goods, each in its own transaction. And let's make these transactions nested in the third. Next, we will artificially cause an error in the first transaction using the method Raise Exception:

&OnServerWithout Context Procedure RunTransactionOnServer() StartTransaction() ; Attempt to StartTransaction(); Product = Directories. Goods. CreateItem() ; Product. Name = "Table" ; Product. Write() ; Raise Exception "Product entry error."; CommitTransaction() ; Exception CancelTransaction() ; Message = New MessageToUser; Message. Text = ErrorDescription() AttemptStartTransaction() ; Product = Directories. Goods. CreateItem() ; Product. Name = "Chair" ; Product. Write() ; CommitTransaction() ; Exception CancelTransaction() ; Message = New MessageToUser; Message. Text = ErrorDescription() ; Message. To report() ; EndAttempt ; CommitTransaction() ; End of Procedure

As a result of performing this procedure, we will see the following in the message window:

(ExternalProcessing.TransactionsAtTrying.Form.Form.Form(20)): Error writing item. (ExternalProcessing.TransactionsAtTrying.Form.Form.Form(40)): Error when calling context method (Write): Errors have already occurred in this transaction!

Thus, organizing nested transactions in 1C is absolutely pointless.

Possible options

Now let's go back to the option where we recorded the product and the price for it. If we have an error while performing a transaction, it will be difficult to understand at what point it occurred - when recording the product or when recording the price, since both occur within the same attempt. To determine where the error occurred, we need to wrap each write operation in its own attempt and avoid nested transactions. To do this, we introduce a Boolean variable Refusal and depending on its value at the end of all operations we will commit or cancel the transaction.

&OnServerWithout Context Procedure RunTransactionOnServer() // Start the transaction Refuse = False ; StartTransaction() ; // Trying to record the product Attempt Product = Directories. Goods. CreateItem() ; Product. Name = "Hole Punch" ; Product. Write() ; Exception Failure = True ; Message = New MessageToUser; Message. Text = "Error recording product"; Message. To report() ; EndAttempt ; // Trying to record the price AttemptRecordSet = InformationRegisters. Price. CreateRecordSet() ; NewRecord = RecordSet. Add() ; NewRecord. Period = CurrentDate() ; NewRecord. Product = Product. Link; NewRecord. Amount = 100 ; RecordSet. Write() ; Exception Failure = True ; Message = New MessageToUser; Message. Text = "Error while recording price"; Message. To report() ; EndAttempt ; // Commit or cancel the transaction If NOT Failure Then CommitTransaction() ; Else CancelTransaction() ; EndIf ; End of Procedure

We can do the same thing when we iterate and write any data in a loop. In this case, we will be able to obtain a list of all data with errors, if any.

In preparation for 1C Expert certification, on the eve of two very important and global topics - blocking, I would like to look at something without which the above concepts are impossible - a DBMS transaction.

Transaction- a logically connected, indivisible sequence of actions. The transaction can either be completed in its entirety or not at all. To commit a transaction in the DBMS, the COMMIT method is used.

A typical example of a transaction is a transfer Money from one account to another:

  1. start a transaction;
  2. read the amount of funds in account number 123;
  3. reduce account balance 123 by 100 rubles;
  4. save account balance number 123;
  5. read the amount of funds in account number 321;
  6. increase your balance by 100 rubles;
  7. record the new amount of funds in account 321;
  8. commit the transaction.

Get 267 video lessons on 1C for free:

As we can see, if a transaction is not completed completely, then it has no meaning.

Key requirements (ACID) for a transactional DBMS

One of the most common sets of requirements for transactions and transactional DBMSs is the ACID (Atomicity, Consistency, Isolation, Durability) set. These are the properties that any transaction must have:

  • Atomicity— no transaction should be recorded partially;
  • Consistency- the system is in a consistent state before the transaction begins and must remain in a consistent state after the transaction is completed;
  • Isolation— during the execution of a transaction, parallel transactions should not affect its result;
  • Durability- in the event of a failure, changes made by a successfully completed transaction must remain saved after the system returns to operation.

Transactions in 1C

Transactions in 1C 8.3 and 8.2 are created both automatically and described by the developers.

You can use the TransactionActive() method to find out whether a transaction is active.

An example of an automatic transaction is processing a document posting, writing a directory item to a database, writing a set of information register records, etc.

The title came out catchy, but it boiled over. I’ll say right away that we’ll be talking about 1C. Dear 1C users, you do not know how to work with transactions and do not understand what exceptions are. I came to this conclusion by looking through a large number of 1C code, born in the wilds of the domestic enterprise. IN typical configurations this is all good enough, but an appalling amount of custom code is written incompetently from a database point of view. Have you ever seen the error “This transaction has already encountered errors”? If yes, then the title of the article applies to you too. Let's finally figure out what transactions are and how to handle them correctly when working with 1C.

Why should we sound the alarm?

First, let's figure out what the "Errors have already occurred in this transaction" error is. This is, in fact, an extremely simple thing: you are trying to work with the database inside an already rolled back (cancelled) transaction. For example, somewhere the CancelTransaction method was called, and you are trying to commit it.


Why is that bad? Because this error tells you nothing about where the problem actually happened. When support receives a screenshot with such text from a user, and especially for server code, with which a person does not work interactively is... I wanted to write “critical error”, but I thought that this was a buzzword that no one pays attention to anymore... This is an ass. This is a programming error. This is not a random glitch. This is a bug that needs to be fixed immediately. Because when your background server processes go down at night and the company starts rapidly losing money, then “Errors have already occurred in this transaction” is the last thing you want to see in the diagnostic logs.


There is, of course, a possibility that the server’s technological log (it’s turned on in production, right?) will somehow help diagnose the problem, but right now I can’t think of an option off the top of my head - how exactly to find the real cause of this error in it. But the real reason is one - the programmer Vasya received an exception within a transaction and decided that once was not a bad idea, “just think, it’s a mistake, let’s move on.”

What are transactions in 1C

It’s awkward to write about elementary truths, but apparently a little will be necessary. Transactions in 1C are the same as transactions in a DBMS. These are not some special “1C” transactions, these are transactions in the DBMS. According to the general idea of ​​transactions, they can either be executed entirely or not executed at all. All changes to database tables made within a transaction can be immediately undone, as if nothing had happened.


Next, you need to understand that 1C does not support nested transactions. As a matter of fact, they are not supported “in 1C”, but not supported at all. At least those DBMSs that 1C can work with. Nested transactions, for example, do not exist in MS SQL and Postgres. Each “nested” call to StartTransaction simply increments the transaction counter, and each call to “CommitTransaction” simply decreases this counter. This behavior is described in many books and articles, but the conclusions from this behavior are apparently not sufficiently analyzed. Strictly speaking, in SQL there is a so-called. SAVEPOINT, but 1C does not use them, and this thing is quite specific.



Procedure Very Useful and Important Code(List of Directory Links) StartTransaction(); For Each Link From the List of Directory Links Loop Directory Object = Link.GetObject(); Directory Object.SomeField = "I was changed from program code"; Directory object.Write(); EndCycle; CommitTransaction(); End of Procedure

Code in English

Not really. I absolutely don’t want to duplicate examples in English just to amuse fans of holy wars and holy wars.


You probably write code like this, right? The code example provided contains errors. At least three. Do you know which ones? I will say right away about the first one; it is related to object locks and is not directly related to transactions. About the second - a little later. The third error is a deadlock, which will occur during parallel execution of this code, but this is a topic for a separate article; we will not consider it now, so as not to complicate the code. Keyword for googling: deadlock controlled locks.


Please note that the code is simple. There is simply a lot of this in your 1C systems. And it contains at least 3 errors at once. Think at your leisure how many errors there are in more complex scenarios for working with transactions written by your 1C programmers :)

Object locks

So, the first mistake. In 1C there are object locks, the so-called “optimistic” and “pessimistic”. I don’t know who coined the term, I would have killed him :). It is absolutely impossible to remember which of them is responsible for what. They have been written about in detail, as well as in other general IT literature.


The essence of the problem is that in the specified code example, a database object is changed, but in another session there may be an interactive user (or a neighboring background thread) who will also change this object. Here, one of you may receive the error "the entry has been modified or deleted." If this happens in an interactive session, the user will scratch his butt, swear, and try to reopen the form. If this happens in a background thread, then you will have to look for it in the logs. And the logbook, as you know, is slow, and only a few people in our industry set up the ELK stack for 1C logs... (we, by the way, are among those who set up and help others set up :))


In short, this is an annoying mistake and better not to have it. Therefore, the development standards clearly state that before changing objects, it is necessary to place an object lock on them using the " Directory object.Lock()". Then the concurrent session (which must also do this) will not be able to start the update operation and will receive the expected, controlled failure.

And now about transactions

We've dealt with the first mistake, let's move on to the second.


If you do not provide exception checking in this method, then an exception (for example, very likely in the "Write()" method) will throw you out of this method without completing the transaction. An exception from the “Write” method can be thrown for a variety of reasons, for example, some application checks in the business logic are triggered, or the above-mentioned object lock occurs. Anyway, the second error says: The code that started the transaction is not responsible for its completion.



That's exactly what I would call this problem. In our static 1C code analyzer based on SonarQube, we even separately built in such diagnostics. Now I’m working on its development, and the imagination of 1C programmers, whose code comes to me for analysis, sometimes leaves me in shock and awe...


Why? Because an exception thrown at the top inside a transaction in 90% of cases will not allow this transaction to be committed and will lead to an error. It should be understood that 1C automatically rolls back an unfinished transaction only after returning from the script code to the platform code level. As long as you are at the 1C code level, the transaction remains active.


Let's go up one level in the call stack:


Procedure ImportantCode() LinkList = GetLinkListWhere(); VeryUsefulAndImportantCode(LinkList); End of Procedure

Look what happens. Our problematic method is called from somewhere outside, higher up the stack. At the level of this method, the developer has no idea whether there will be any transactions inside the Very Useful and Important Code method or not. And if there are, will they all be completed... We are all here for peace and encapsulation, right? The author of the "ImportantCode" method should not think about what exactly happens inside the method he calls. The same one in which the transaction is processed incorrectly. As a result, an attempt to work with the database after an exception has been thrown from within a transaction will most likely result in the following: “In this transaction blah blah...”

Spreading transactions across methods

The second rule of "transaction-safe" code: The transaction reference count at the beginning of the method and at its end must have the same value. You cannot start a transaction in one method and end it in another. It is probably possible to find exceptions to this rule, but this will be some kind of low-level code written by more competent people. In general, you can't write this way.


For example:


Procedure ImportantCode() LinkList = GetLinkListWhere(); VeryUsefulAndImportantCode(LinkList); CommitTransaction(); // A ticket to hell, a serious conversation with the author about our complex labor relations. End of Procedure

The above is unacceptable shit code. You can't write methods so that the caller remembers and keeps track of possible (or probable - who knows) transactions within other methods that it calls. This is a violation of encapsulation and a proliferation of spaghetti code that cannot be traced with one's sanity.


It's especially fun to remember that the real code is much larger than the synthetic 3-line examples. Finding starting and ending transactions at six levels of nesting - this directly motivates intimate conversations with the authors.

Trying to fix the code

Let's go back to the original method and try to fix it. I’ll say right away that we won’t fix the object lock for now, just so as not to complicate the example code.

The first approach of a typical 1C nickname

Typically, 1C programmers know that an exception may be thrown when recording. They are also afraid of exceptions, so they try to catch them all. For example, like this:


Procedure Very Useful and Important Code(List of Directory Links) StartTransaction(); For Each Link From the List of Directory Links Loop Directory Object = Link.GetObject(); Directory Object.SomeField = "I was changed from program code"; AttemptDirectoryObject.Write(); Exception Log.Error("Could not write element %1", Link); Continue; EndAttempt; EndCycle; CommitTransaction(); End of Procedure

Well, things have gotten better, right? After all, now possible mistakes records are processed and even logged. Exceptions will no longer be thrown when writing an object. And in the log you can even see on which object, I wasn’t too lazy and included a link in the message instead of the laconic “Error in writing a directory,” as developers who are always in a hurry often like to write. In other words, there is a concern for the user and an increase in competencies.


However, an experienced 1C user here will say that no, it hasn’t gotten better. In fact, nothing has changed, and maybe even got worse. In the “Write()” method, the 1C platform itself will start a write transaction, and this transaction will already be nested in relation to ours. And if, while working with the 1C database, the transaction rolls back (for example, a business logic exception is thrown), then our top-level transaction will still be marked as “corrupted” and cannot be committed. As a result, this code will remain problematic, and when you try to commit it will display “errors have already occurred.”


Now imagine that we are not talking about a small method, but about a deep call stack, where at the very bottom someone took and “released” the started transaction from their method. Top-level procedures may have no idea that anyone down there has started transactions. As a result, the entire code fails with a vague error that is impossible to investigate in principle.


The code that starts a transaction is required to complete or rollback it. Regardless of any exceptions. Each code branch should be examined to see if a method exits without committing or canceling the transaction.

Methods of working with transactions in 1C

It would not be superfluous to remind you what 1C generally provides us with for working with transactions. These are the well-known methods:

  • StartTransaction()
  • CommitTransaction()
  • CancelTransaction()
  • TransactionActive()

The first 3 methods are obvious and do what their names say. The last method returns True if the transaction counter is greater than zero.


And there is an interesting feature. The transaction exit methods (Commit and Cancel) throw exceptions if the transaction count is zero. That is, if you call one of them outside of a transaction, an error will occur.


How to use these methods correctly? It’s very simple: you need to read the rule formulated above:


How to comply with this rule? Let's try:


We already understood above that the Do Something method is potentially dangerous. It may throw some kind of exception, and the transaction will “crawl out” of our method. Okay, let's add a possible exception handler:


StartTransaction(); Try DoSomething(); Exception // but what should I write here? EndAttempt; CommitTransaction();

Great, we caught the error that was occurring, but what should we do about it? Write a message to the log? Well, maybe if the error logging code should be exactly at this level and we are waiting for an error here. And if not? What if we didn't expect any errors here? Then we should just pass that exception up and let another layer of the architecture deal with it. This is done with the "CauseException" operator without arguments. In these Javascripts of yours, this is done in exactly the same way with the throw operator.


StartTransaction(); Try DoSomething(); Exception ThrowException; EndAttempt; CommitTransaction();

So, wait... If we just throw the exception further, then why is there a need for an Attempt at all? Here's why: the rule forces us to ensure the completion of the transaction we started.


StartTransaction(); Try DoSomething(); ExceptionCancelTransaction(); throwException; EndAttempt; CommitTransaction();

Now it seems to be beautiful. However, we remember that we do not trust the Do Something() code. What if the author inside didn’t read this article and doesn’t know how to work with transactions? What if he took it there and called the CancelTransaction method or, on the contrary, committed it? It is very important for us that the exception handler did not throw a new exception, otherwise the original error will be lost and troubleshooting will become impossible. And we remember that the Commit and Cancel methods can throw an exception if the transaction does not exist. This is where the TransactionActive method comes in handy.

Final version

Finally, we can write the correct, "transaction-safe" version of the code. Here he is:


**UPD: the comments suggested a safer option when CommitTransaction is located inside the Attempt block. This particular option is shown here; previously Fixation was located after the Attempt-Exception block.


StartTransaction(); Try DoSomething(); CommitTransaction(); Exception If TransactionIsActive() Then CancelTransaction(); endIf; throwException; EndAttempt;

Wait, but it’s not only “CancelTransaction” that can produce errors. Why then isn't "CommitTransaction" wrapped in the same condition with "TransactionActive"? Again, using the same rule: The code that started the transaction should be responsible for completing it. Our transaction is not necessarily the very first; it can be nested. At our level of abstraction, we are only required to care about our transaction. All others should be of no interest to us. They are strangers, we should not be responsible for them. Precisely they SHOULD NOT. No attempt should be made to determine the actual transaction counter level. This will again break encapsulation and lead to “smearing” transaction management logic. We only checked for activity in the exception handler and only to make sure that our handler will not generate a new exception, “hiding” the old one.

Refactoring checklist

Let's look at some of the most common situations that require code intervention.


Pattern:


StartTransaction(); DoSomething(); CommitTransaction();

Wrap it in a “safe” design with an Attempt, Keep Alive and Throw an Exception.


Pattern:


If NotTransactionActive() ThenStartTransaction()EndIf

Analysis and Refactoring. The author didn't understand what he was doing. It is safe to start nested transactions. There is no need to check the condition, you just need to start the nested transaction. Below in the modulus, it is probably still distorted there with their fixation. This is guaranteed hemorrhoids.


A roughly similar option:


If Transaction is Active() Then CommitTransaction() EndIf

similarly: committing a transaction by condition is strange. Why is there a condition here? What, someone else could have already recorded this transaction? Reason for trial.


Pattern:


StartTransaction() While Select.Next() Loop // reading an object by reference // writing an object EndCycle; CommitTransaction();
  1. introduce controlled locking to avoid deadlock
  2. enter a call to the Block method
  3. wrap in "try" as shown above

Pattern:


StartTransaction() While Select.Next() Loop Attempt Object.Write(); Exception Report("Failed to write"); EndAttempt; EndCycle; CommitTransaction();

This transaction will no longer complete in the event of an exception. There's no point in continuing the cycle. The code needs to be rewritten, checking the original task. Additionally provide a more informative error message.

Finally

I, as you probably already guessed, am one of the people who loves the 1C platform and development on it. Of course, there are complaints about the platform, especially in the Highload environment, but in general, it allows you to inexpensively and quickly develop very high-quality enterprise applications. Providing out of the box an ORM, a GUI, a web interface, Reporting, and much more. In the comments on Habré they usually write all sorts of arrogant things, so guys - the main problem with 1C, as an ecosystem, is not a platform or a vendor. This is too low a threshold for entry, which allows people to enter the industry who do not understand what a computer, database, client-server, network and all that is. 1C has made enterprise application development too easy. In 20 minutes I can write an accounting system for purchases/sales with flexible reports and a web client. After this, it’s easy for me to think to myself that on a larger scale you can write in much the same way. Somehow 1C will do everything internally, I don’t know how, but it will probably do it. Let me write "StartTransaction()"....

Add tags