System.Transactions infrastructure in the .NET world

    Have you met a C # type construct using (var scope = new TransactionScope(TransactionScopeOption.Required))? This means that the code running in the block usingis in a transaction and after exiting this block, the changes will be committed or canceled. It sounds clear until you start digging deeper. And the deeper you dig, the “stranger and stranger” it becomes. Anyway, when I got closer acquainted with the class TransactionScopeand the .NET transactions in general, a whole lot of questions arose.

    What kind of class TransactionScope? As soon as we use the design using (var scope = new TransactionScope()), everything in our program immediately becomes transactional? What is a “Resource Manager” and a “Transaction Manager”? Is it possible to write your resource manager and how it “connects” to the created instanceTransactionScope? What is a distributed transaction and is it true that a distributed transaction in SQL Server or Oracle Database is the same as a .NET distributed transaction?

    In this publication, I tried to collect material that helps find answers to these questions and form an understanding of transactions in the .NET world.


    What are transactions and what problems do they solve?

    The transactions in question are operations that transfer the system from one acceptable state to another and are guaranteed not to leave the system in an unacceptable state even in the event of unforeseen situations. What kind of acceptable states are, in general, depends on the context. Here we will consider an acceptable situation in which the data we process is complete. This implies that the changes that make up the transaction, all together or made, or not made. In addition, changes to one transaction can be isolated from changes made to the system by another transaction. Key transaction requirements are abbreviated as ACID. For the first acquaintance with them fit the article in the "Wikipedia" .

    A classic example of a transaction is the transfer of money between two accounts. In this situation, withdrawing money from account No. 1 without crediting to account No. 2 is unacceptable, in the same way as crediting to account No. 2 without withdrawing from account No. 1. In other words, we want both operations - both withdrawal and crediting - run immediately. If some of them fail, then the second operation should not be performed. You can call this principle "all or nothing." Moreover, it is desirable that operations are performed synchronously even in the event of system failures such as a power outage, that is, that we see the system in an acceptable state as soon as it becomes available after recovery.

    In mathematical terms, we can say that there is an invariant relative to the system, which we would like to preserve. For example, the amount on both accounts: it is necessary that after the transaction (money transfer) the amount remains the same as before it. By the way, in the classic example with the transfer of money, accounting also appears - the subject area, where the notion of a transaction naturally arose.

    We illustrate the example of the transfer of money between two accounts. The first picture shows the situation when the transfer of 50 rubles from account number 1 to account number 2 was completed successfully. Green indicates that the system is in an acceptable state (the data is consistent).

    Now let's imagine that the transfer is made outside the transaction and after the withdrawal of money from account No. 1 there was a failure, due to which the withdrawn money was not credited to account No. 2. The system will be in an unacceptable state (red).

    If the error occurred between the withdrawal and crediting operations, but the transfer was carried out within one transaction, the withdrawal operation will be canceled. As a result, the system will remain in its original acceptable state.

    I will give examples of situations from the experience of our company in which transactions are useful: accounting for goods (accounting for the amount of goods of different types that are in certain stores and on the way), accounting for storage resources (accounting for the volume of space occupied by goods of a certain type, volume of space, free to place the goods, the amount of goods that employees can move and automated storage systems for the day).

    The problems that arise when data integrity is compromised are obvious. The information provided by the system does not just become unreliable - it loses touch with reality and turns into nonsense.

    What transactions are considered here

    Transaction benefits are known. So, to maintain data integrity, we need a relational database, because this is where transactions are made? Not really. It was said above that the concept of a transaction depends on the context, and now we will briefly consider which transactions we can talk about when discussing information systems.

    First, let's separate the concepts of subject domain transactions (business transactions) and system transactions. The latter can be implemented in different places and in different ways.

    Let's enter from the highest level - the subject area. An interested person can declare that there are some acceptable states and he does not want to see the information system outside these states. Let's not invent unnecessary examples: transferring money between accounts is suitable here. Just to clarify that a transfer is not necessarily a transfer of money between the settlement accounts of two bank customers. Equally important is the task of accounting, when accounts must reflect the sources and purpose of the funds of the organization, and the transfer - a change in the distribution of funds for these sources and destinations. This was an example of a domain transaction .

    Now let's see the most common and interesting examples of the implementation of system transactions. In system transactions, various technical means provide the requirements of the subject area. A classic, proven solution of this kind is relational DBMS transactions (the first example). Modern database management systems (both relational and non- relational ) provide a transaction mechanism that allows you to either save (fix) all changes made during a specified period of work, or discard them (roll back). When using such a mechanism, the operations of withdrawing money from one account and crediting it to another account, which constitute the transaction of the subject area, will be combined by means of the DBMS into a system transaction and either executed together or not executed at all.

    Using the DBMS is, of course, not necessary. Roughly speaking, you can generally implement the DBMS transaction mechanism in your favorite programming language and enjoy the unstable and error-prone analogue of existing tools. But your “bicycle” can be optimized for specific situations in the subject area.

    There are more interesting options. Modern industrial programming languages ​​(C # and Java in the first place) offer tools designed specifically for organizing transactions involving completely different subsystems, and not just a DBMS. In this publication we will call such transactions software. In the case of C #, these are transactions from the System.Transactions namespace (second example), and this is described below.

    Before moving on to transactionsSystem.TransactionsOne cannot fail to mention another interesting phenomenon. The tools System.Transactionsallow the programmer to independently realize the software transactional memory . In this case, software operations that affect the state of the system (in the case of classical imperative programming languages, this is an assignment operation) are included by default in transactions that can be fixed and rolled back in much the same way as DBMS transactions. With this approach, the need to use synchronization mechanisms (in C # - lock, in Java - synchronized) is greatly reduced. Further development of this idea is software transactional memory supported at the platform level.(third example). Such a miracle is expectedly found in a language whose elegance surpasses its industrial applicability, Clojure. And for worker-peasant languages ​​there are plug-in libraries that provide the functionality of software transactional memory.

    System transactions can include several information systems, in which case they become distributed. Both DBMS transactions and software transactions can be distributed; it all depends on what kind of functionality the particular transaction implementation tool supports. Details of distributed transactions are discussed in the corresponding section. I will give a picture to make it easier to understand the items under discussion.

    TL; DR by section

    There are processes that consist of several indivisible (atomic) operations applied to the system, generally not necessarily informational. Each indivisible operation can leave the system in an unacceptable state when the integrity of the data is broken. For example, if the transfer of money between two accounts is represented by two indivisible operations of withdrawing from account No. 1 and crediting to account No. 2, then performing only one of these operations will violate the integrity of the data. Money or disappear unknown where, or appear from nowhere. A transaction combines indivisible operations so that they are performed all together (of course, sequentially, if necessary), or not performed at all. You can talk about subject-matter transactions and transactions in technical systems, which usually implement subject-matter transactions.

    Transactions based on System.Transactions

    What is it

    In the .NET world, there is a software framework designed by the creators of a transaction management platform. From the point of view of the programmer who uses transactions, this type of frame consists of TransactionScope, TransactionScopeOption, TransactionScopeAsyncFlowOptionand  TransactionOptionsnamespace System.Transactions. If we talk about .NET Standard, then all this is available starting with  version 2.0 .

    Namespace transactions are System.Transactionsbased on  the X / Open XA standard from The Open Group consortium . This standard introduces many of the terms discussed below, and, most importantly, describes distributed transactions, to which a special section is also devoted to this publication. Software transactions are also based on this standard in other platforms, such as Java..

    A typical transaction usage scenario for a C # programmer is as follows:

    using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required))
        // Какой-то транзакционный код, например работа с СУБД.

    Inside the block usingis placed the code that performs the work, the results of which should be recorded or canceled all together. Classic examples of such work are reading and writing to the database or sending and receiving messages from a queue. When control leaves the unit using, the transaction will be committed. If you remove the call Complete, the transaction will be rolled back. Pretty simple.

    It turns out that during a transaction rollback, all transactions made inside such a block usingwill be canceled? And if I assigned some variable a different value, then this variable will restore the old value? When I first saw a similar design, I thought so. In fact, of course, not all changes will be rolled back, but only very special ones.. If all changes had been rolled back, then this would have been the software transactional memory described above. And now let's see what kind of special changes that can participate in program-based transactions System.Transactions.

    Resource managers

    In order for something to support transactions on the basis System.Transactions, it is necessary that it has information that transaction work is in progress and that it is registered in some register of participants in the transaction. You can get information about whether transaction work is going on by checking the static Currentclass property System.Transactions.Transaction. An input to the block of the usingabove kind just sets this property if it has not been set before. And to register as a party to a transaction, you can use type methods . In addition, you need to implement the interface required by these methods. A resource manager (Resource Manager) is exactly that “something” that supports interaction with transactions from  (a more specific definition is given below).Transaction.EnlistSmthSystem.Transactions

    What are resource managers? If we work from C # with a DBMS, for example, SQL Server or Oracle Database, we usually use the appropriate drivers, and they are the control resources. In the code, they are represented by types System.Data.SqlClient.SqlConnectionand  Oracle.ManagedDataAccess.Client.OracleConnection. Also, they say MSMQ supports transaction based System.Transactions. Guided by the knowledge and examples drawn from the Internet, you can create your own resource manager. The simplest example is in the next section.

    In addition to resource managers, we must also have a transaction manager (Transaction Manager), who will monitor the transaction and promptly give orders to the resource manager. Depending on which resource managers participate in the transaction (which characteristics they have and where they are located), different transaction managers are involved in the work. In this case, the choice of the appropriate version occurs automatically and does not require the intervention of a programmer.

    More specifically, a resource manager is an instance of a class that implements a special interface System.Transactions.IEnlistmentNotification. An instance of the class, as directed by the client, is registered as a participant in the transaction, using the static property System.Transactions.Transaction.Current. Later, the transaction manager calls the methods of the specified interface as necessary.

    Clearly, the set of resource managers involved in a transaction may change at runtime. For example, after entering a block, usingwe can first do something in SQL Server, and then in Oracle Database. Depending on this set of resource managers, the transaction manager used is determined. To be more precise, the transaction protocol being used is determined by the set of resource managers, and the one who supports it is determined by the transaction manager based on the protocol. We'll look at transaction protocols later.when we talk about distributed transactions. The mechanism for automatically selecting the appropriate transaction manager at run time when the resource managers involved in a transaction change is called Transaction Promotion.

    Types of resource managers

    Resource managers can be divided into two large groups: durable and non-permanent.

    Durable Resource Manager is a resource manager that supports a transaction even if the information system is unavailable (for example, when the computer is restarted). Non-permanent resource manager (Volatile Resource Manager) - a resource manager that does not support a transaction if the information system is unavailable. A non-persistent resource manager supports in-memory transactions only.

    Classic durable resource managers are a DBMS (or a DBMS driver for a software platform). No matter what happens - even if the operating system fails, even if the power is turned off - the DBMS will guarantee the integrity of the data after it comes back to a working state. For this, of course, you have to pay some inconveniences, but in this article we will not consider them. An example of a non-persistent resource manager is the software transaction memory mentioned above.

    Using TransactionScope

    When creating an object of a type, TransactionScopeyou can specify some parameters.

    First, there is a setting that tells the runtime environment what you need:

    1. use a transaction that already exists at the moment;
    2. be sure to create a new one;
    3. on the contrary, execute the code inside the block usingoutside the transaction.

    For all this, the transfer is responsible System.Transactions.TransactionScopeOption.

    Second, you can set the transaction isolation level. This is a parameter that allows you to find a compromise between the independence of change and the speed of work. The most independent level — serializable — ensures that there are no situations where changes made within one uncommitted transaction can be seen in another transaction. Each next level adds one such specific situation where simultaneously running transactions can affect each other. By default, a transaction opens at a serializable level, which can be unpleasant (see, for example, this comment ).

    Setting the transaction isolation level when creatingTransactionScopeis advisory in nature for resource managers. They may not even support all levels presented in the listing System.Transactions.IsolationLevel. In addition, it should be borne in mind that when using a connection pool for working with a database, the connection for which the transaction isolation level was changed will retain this level upon returning to the pool . Now, when the programmer receives this connection from the pool and relies on the defaults, he will observe unexpected behavior.

    Typical c work scenarios  TransactionScopeand significant pitfalls (namely, nested transactions) are well covered in  this article on Habré .

    Applicability of software transactions

    It should be said that in almost any information system that is in industrial operation, processes are launched that can lead the system to an unacceptable state. Therefore, it is necessary to control these processes, find out whether the current state of the system is acceptable, and, if not, restore it. Software transactions are a ready-made tool for keeping the system in acceptable condition.

    In each case it would be constructive to consider the cost:

    1. integration of processes into the software transaction infrastructure (these processes need to be aware of  TransactionScopeand many other things);
    2. maintaining this infrastructure (for example, the cost of renting equipment with Windows on board);
    3. employee training (since the topic of .NET transactions is not very common).

    We should not forget that the transaction process may be required to report its progress to the "outside world", for example, to keep a log of actions outside the transaction.

    Obviously, the rejection of software transactions will require the creation or implementation of some other means of maintaining data integrity, which will also have its value. In the end, there are cases when data integrity problems are so rare that it is easier to restore the acceptable state of the system with surgical interventions than to maintain an automatic recovery mechanism.

    Example of non-permanent resource manager

    Now consider an example of a simple resource manager that does not support recovery from a system failure. We will have a block of software transactional memory storing some value that can be read and rewritten. In the absence of a transaction, this block behaves like a normal variable, and in the presence of a transaction, it retains an initial value that can be restored after the transaction is rolled back. The code for such a resource manager is as follows:

    internalsealedclassStm<T> : System.Transactions.IEnlistmentNotification {
        private T _current;
        private T _original;
        privatebool _enlisted;
        public T Value {
            get { return _current; }
            set {
                if (!Enlist()) {
                    _original = value;
                _current = value;
        publicStm(T value) {
            _current = value;
            _original = value;
        privateboolEnlist() {
            if (_enlisted)
            var currentTx = System.Transactions.Transaction.Current;
            if (currentTx == null)
            currentTx.EnlistVolatile(this, System.Transactions.EnlistmentOptions.None);
            _enlisted = true;
        #region IEnlistmentNotificationpublicvoidCommit(System.Transactions.Enlistment enlistment) {
            _original = _current;
            _enlisted = false;
        publicvoidInDoubt(System.Transactions.Enlistment enlistment) {
            _enlisted = false;
        publicvoidPrepare(System.Transactions.PreparingEnlistment preparingEnlistment) {
        publicvoidRollback(System.Transactions.Enlistment enlistment) {
            _current = _original;
            _enlisted = false;
        #endregion IEnlistmentNotification

    It can be seen that the only formal requirement is the implementation of the interface System.Transactions.IEnlistmentNotification. From interesting things worth noting methods Enlist(not part System.Transactions.IEnlistmentNotification) and  Prepare. The method Enlistjust checks if the code works as part of a transaction, and, if so, registers an instance of its class as a non-persistent resource manager. The method Prepareis invoked by the transaction manager before committing the changes. Our resource manager signals its readiness to commit by calling a method System.Transactions.PreparingEnlistment.Prepared.

    The following is a code showing an example of using our resource manager:

    var stm = new Stm<int>(1);
    using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) {
        stm.Value = 2;

    If immediately after exiting the block usingto read the property stm.Value, then the set value is expected there  2. And if you remove the call scope.Complete, the transaction will be rolled back and the property stm.Valuewill have the value  1set before the transaction.

    Simplified sequence of calls when working with transactions is System.Transactionsshown in the diagram below.

    It can be seen that in this example not all the possibilities provided by the infrastructure are considered System.Transactions. We will take a closer look at them after we become familiar with transactional protocols and distributed transactions in the next section.

    TL; DR by section

    A programmer can use a class TransactionScopeto execute some code within an existing or new transaction. A transaction is committed if and only if a TransactionScopemethod is called on an existing instance of a class Dispose, despite the fact that before that it had calledComplete. The programmer can indicate whether he wants to start a new transaction without fail, use an existing one, or, conversely, execute a code outside of an existing transaction. Only resource managers are involved in the transaction - software components that implement certain functionality. Resource managers can be long-lasting (recovering from a system failure) and non-permanent (not recovering). A DBMS is an example of a durable resource manager. The coordination of resource managers is handled by the transaction manager — a software component that is automatically selected by the runtime environment without the participation of the programmer.

    A non-persistent resource manager is a class that implements the interface System.Transactions.IEnlistmentNotificationand in the method.Prepareconfirming its readiness to commit changes or, on the contrary, signaling a rollback of changes. When the caller does something with the resource manager, it checks to see if the transaction is open now, and, if open, is registered with the method System.Transactions.Transaction.EnlistVolatile.

    Distributed transactions

    What is it

    A distributed transaction involves several information subsystems (in fact, not everything is so simple, below is described in more detail). It is understood that changes in all systems participating in a distributed transaction must either be committed or rolled back.

    Above were presented various means of transaction implementation: a DBMS, an infrastructure System.Transactions, a software transactional memory built into the platform. These tools can also provide distributed transactions. For example, in Oracle Database, changing (and actually reading) data in several databases within a single transaction automatically turns it into a distributed one. Then we will talk about software distributed transactions, which can include heterogeneous resource managers.

    Transactional Protocols

    Transaction Protocol is a set of principles by which applications involved in a transaction interact. The following protocols are most common in the .NET world.

    Lightweight. A maximum of one durable resource manager is used. All transactional interactions occur within the same application domain, or the resource manager supports promotion and single-phase commit (implements IPromotableSinglePhaseNotification).

    OleTx. Interworking between multiple application domains and multiple computers is allowed. It is allowed to use many durable resource managers. All participating computers must be running Windows. Uses remote procedure call (RPCs).

    WS-AT.Interworking between multiple application domains and multiple computers is allowed. It is allowed to use many durable resource managers. The participating computers may be running different operating systems, not just Windows. The hypertext transfer protocol (Hypertext Transmission Protocol, HTTP) is used.

    It was noted above that the current transactional protocol influences the choice of the transaction manager, and the choice of the protocol is influenced by the characteristics of the control resources involved in the transaction. We now list the known transaction managers.

    Lightweight Transaction Manager (LTM) . Introduced in the .NET Framework 2.0 and later. Manages transactions using the Lightweight protocol.

    Kernel Transaction Manager (KTM). Introduced in Windows Vista and Windows Server 2008. Manages transactions using the Lightweight protocol. It can call a transactional file system (Transactional File System, TxF) and a transaction registry (Transactional Registry, TxR) in Windows Vista and Windows 2008.

    Distributed Transaction Coordinator (MSDTC) . Manages transactions using the OleTx and WS-AT protocols.

    It should also be borne in mind that some of the resource managers do not support all the protocols listed. For example, MSMQ and SQL Server 2000 do not support Lightweight, so transactions involving MSMQ or SQL Server 2000 will be managed by MSDTC, even if they are the only participants. Technically, this limitation arises from the fact that the specified resource managers, implementing, of course, the interfaceSystem.Transactions.IEnlistmentNotificationdo not implement the interface System.Transactions.IPromotableSinglePhaseNotification. In it there is, among other things, a method Promotethat the runtime environment calls, if necessary, to switch to a more “cool” transaction manager.

    Now the ambiguity of the concept of a distributed transaction should become apparent. For example, you can define a distributed transaction as a transaction in which it participates:

    1. at least two any resource managers;
    2. as many non-permanent resource managers and at least two durable ones;
    3. at least two of any resource managers, necessarily located on different computers.

    Therefore, it is always best to clarify exactly which transactions in question.

    And in this context, MSDTC is primarily discussed. It is a software component of Windows that manages distributed transactions. There is a graphical interface for configuration and monitoring of transactions, which can be found in the “Component Services” utility by following the path “Computers - My Computer - Distributed Transaction Coordinator - Local DTC”.

    For configuration, select the “Properties” item in the context menu of the “Local DTC” node, and to monitor distributed transactions, select the “Transaction statistics” item in the central panel.

    Biphasic fixation

    If several resource managers are involved in the transaction, the results of their work may differ: for example, one of them has succeeded, and he is ready to commit the changes, and the other has an error, and he is going to roll back the changes. However, the essence of a distributed transaction lies precisely in the fact that the changes of all the controlling resources involved in the transaction are either fixed all together or rolled back. Therefore, in such cases, a two-phase commit protocol is usually used.

    In general, the essence of this protocol is as follows. During the first phaseresource managers involved in the transaction prepare enough information to recover from a failure (if it is a durable resource manager) and to successfully complete the work as a result of committing. From a technical point of view, the resource manager signals that he has completed the first phase, calling the method System.Transactions.PreparingEnlistment.Preparedin the method Prepare. Or the resource manager can notify about the rollback of changes by calling the method ForceRollback.

    When all involved in the transaction resource managers "voted", that is, notified the transaction manager about whether they want to commit or roll back the changes, the second phase begins. At this time, resource managers are instructed to record their changes (if all participants voted for fixation) or to refuse changes (if at least one participant voted for rollback). Technically, this is expressed in calling methods Commitand  methods Rollbackthat implement resource managers and in which they call the method System.Transactions.Enlistment.Done.

    The resource manager can call the method System.Transactions.Enlistment.Doneduring the first phase. In this case, it is assumed that he is not going to record any changes (for example, works only for reading) and will not participate in the second phase. More information about the two-phase fixation can be found at Microsoft .

    If the connection between the transaction manager and at least one of the resource managers is lost, then the transaction becomes frozen (“in doubt”, in-doubt). The transaction manager, by invoking methods InDoubt, notifies available resource managers about this event, which can respond appropriately.

    There is also a three-phase fixation and its modifications with its advantages and disadvantages. The three-phase commit protocol is less common, perhaps because it requires even more expenses for messages between interacting subsystems.

    Cheat Sheet Interfaces System.Transactions

    Something difficult turns out. To sort things out a bit, I will briefly describe the basic namespace interfaces System.Transactionsneeded to create a resource manager. Here is the class diagram.

    IEnlistmentNotification. The resource manager must implement this interface. The transaction manager calls the implemented methods in the following order. During the first phase, he calls the method Prepare(unless the stars came together to call the method ISinglePhaseNotification.SinglePhaseCommit, as described in the next paragraph). As part of this method, the resource manager saves the information necessary for recovery after a failure, prepares for final fixation of changes on its side and votes for the fixation or rollback of changes. If there comes a second phase, depending on the availability of resources and control of the results of the vote Managing transactions is one of three methods: Commit, InDoubt, Rollback.

    ISinglePhaseNotification.The resource manager implements this interface if it wants to provide the transaction manager with the ability to optimize execution by reducing the second phase of commit. If the transaction manager sees only one resource manager, then in the first fixation phase he tries to call the method SinglePhaseCommit(instead of IEnlistmentNotification.Prepare) to the resource manager and thereby eliminate the vote and the transition to the second phase. This approach has advantages and disadvantages, which are most clearly written by Microsoft here .

    ITransactionPromoter. The resource manager implements this interface (not directly, but through the interfaceIPromotableSinglePhaseNotification), if he wants to give the transaction manager the ability to adhere to the Lightweight protocol even with a remote call, until other conditions occur that will require complication of the protocol. When the protocol needs to be complicated, a method will be called Promote.

    IPromotableSinglePhaseNotification. The resource manager implements this interface so that, first, it implements the interface ITransactionPromoter, and secondly, the transaction manager can use single-phase commit by calling the IPromotableSinglePhaseNotification.SinglePhaseCommitand  methods IPromotableSinglePhaseNotification.Rollback. The transaction manager calls the method IPromotableSinglePhaseNotification.Initializeto mark the successful registration of the resource manager using a simplified scheme. More or less, this can be understood from the  Microsoft document .

    A little more look at the classSystem.Transactions.Enlistmentand his heirs. Instances of this type provide a transaction manager when calling methods on interfaces implemented by a resource manager.

    Enlistment. The resource manager can call a single method of this type, - Done, to signal the successful completion of its part of the work.

    PreparingEnlistment. Using an instance of this type during the first commit phase, the resource manager can signal its intention to commit or roll back the changes. A durable resource manager can also get the information required to recover from a system failure.

    SinglePhaseEnlistment. Using an instance of this type, the resource manager can transmit information to the transaction manager about the results of its work when using the simplified scheme (single-phase commit).

    Software Distributed Transaction Limitations and Alternatives

    A brief survey of opinions found on the Internet shows that in many areas distributed transactions are going out of fashion. Look, for example, at  this mocking comment . The main object of criticism, which is briefly described here , is the synchronous (blocking) nature of distributed transactions. If the user sent a request, during the processing of which a distributed transaction was organized, he will receive a response only after all subsystems included in the transaction have finished (with success or with an error). At the same time, there is an opinion supported by research that the two-phase commit protocol shows poor performance, especially with an increase in the number of subsystems involved in the transaction, which is mentioned, for example, in  this publication on Habré.

    If the creator of the system prefers to return the answer to the user as soon as possible, putting the reconciliation of the data for later, then some other solution is more suitable for him. In the context of the Brewer theorem ( CAP theorems ), it can be said that distributed transactions are suitable for cases where data consistency (Consistency) is more important than availability (Availability).

    There are other practical restrictions on the use of software distributed transactions. For example, it was established experimentally that distributed transactions using the OleTx protocol should not cross network domains. In any case, the long attempts to get them to work were not crowned with success. In addition, it was revealed that the interaction between several copies of the Oracle Database (distributed transaction DBMS) imposes serious restrictions on the applicability of software distributed transactions (again, failed to start).

    What are the alternatives to distributed transactions? First, it must be said that it will be very difficult to do without technical transactions (ordinary, not distributed). There will surely be processes in the system that may temporarily violate the integrity of the data, and it will be necessary to somehow oversee such processes. Similarly, in terms of the subject area, a concept may arise that includes a process that is implemented by a set of processes in different technical systems, which must begin and end in the field of complete data.

    Turning to alternatives to distributed transactions, you can note solutions based on messaging services, such as RabbitMQ and Apache Kafka. In  this publication on "Habré" four such solutions are considered:

    1. the subsystem publishes a message, on the basis of which both the database is written and the other subsystems are notified;
    2. the subsystem writes to the database, and notifications of other subsystems are generated based on the transaction log database (Transaction Log Tailing);
    3. the subsystem leads to the database both the entity table and the message table for external subsystems;
    4. entities in the database are uniquely represented as a queue of records about their creation and changes (Event Sourcing).

    Another alternative is the Saga template. It assumes a cascade of subsystems with its local transactions. Upon completion, each system calls the next one (either alone or with the help of a coordinator). For each transaction, there is a corresponding cancellation transaction, and instead of transferring control further, the subsystem may, on the contrary, initiate the cancellation of changes made by previous subsystems. On "Habré" there are several good articles about the template "Saga". For example,  this publication provides general information about maintaining the ACID principles in the microservices, and in  this article an example of the implementation of the Saga template with the coordinator is discussed in detail.

    In our company, some products successfully use software distributed transactions through WCF, but there are other options. Once, when we tried to make friends with a distributed transaction, we had many problems, including collisions with the limitations described above and parallel troubles with updating the software infrastructure. Therefore, in the conditions of a shortage of resources for running around another capital decision, we used the following tactics. The called party fixes the changes in any case, but notes that they are in the draft state, so these changes do not affect the work of the called system. Then the caller when completing his work through a distributed transaction DBMS activates the changes made by the called system. In this way,

    So is it in .NET Core?

    The .NET Core (and even the .NET Standard) has all the necessary types for organizing transactions and creating your own resource manager. Unfortunately, transactions based on .NET Core System.Transactionshave a serious limitation: they only work with the Lightweight protocol. For example, if the code uses two long-lived resource managers, then at run-time, the environment will throw an exception as soon as the second manager is contacted.

    The fact is that .NET Core is being tried to be independent of the operating system, therefore, the link to transaction managers such as KTM and MSDTC is excluded, and they are needed to support transactions with the specified properties. It is possible that the connection of transaction managers in the form of plug-ins will be realized, but so far this has been written with water, so it’s impossible to count on industrial use of distributed transactions in .NET Core.

    From experience, you can verify the differences in distributed transactions in the .NET Framework and in .NET Core by writing the same code, compiling and running it on different platforms.

    An example of such a code that calls sequentially SQL Server and Oracle Database.

    privatestaticvoidMain(string[] args) {
        using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required)) {
    privatestaticvoidOracle() {
        using (var conn = new Oracle.ManagedDataAccess.Client.OracleConnection("User Id=some_user;Password=some_password;Data Source=some_db")) {
            using (var cmd = conn.CreateCommand()) {
                cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1";
    privatestaticvoidMsSqlServer() {
        var builder = new System.Data.SqlClient.SqlConnectionStringBuilder {
            DataSource = "some_computer\\some_db",
            UserID = "some_user",
            Password = "some_password",
            InitialCatalog = "some_scheme",
            Enlist = true,
        using (var conn = new System.Data.SqlClient.SqlConnection(builder.ConnectionString)) {
            using (var cmd = conn.CreateCommand()) {
                cmd.CommandText = "update t_hello set id_hello = 2 where id_hello = 1";

    Ready-to-build projects are on GitHub .

    Running the example for .NET Core ends in error. The location and type of the thrown exception depend on the order of the DBMS call, but in any case, this exception indicates an invalid transactional operation. Running the example for the .NET Framework ends successfully if MSDTC is running at the time; in this case, in the MSDTC graphical interface, you can observe the registration of a distributed transaction.

    Distributed transactions and WCF

    The Windows Communication Foundation (WCF) is the software framework of the .NET world, designed to organize and invoke network services. Compared with the more fashionable approaches of REST and ASP.NET Web API, it has its own advantages and disadvantages. WCF is very good at making .NET transactions, and in the world of the .NET Framework it is convenient to use it for organizing transactions distributed between a client and a service.

    In .NET Core, this technology works only on the client side, that is, you cannot create a service, but you can only access an existing one. This, however, is not very important, because, as mentioned above, with the distributed transactions in the .NET Core things are not very good at all.

    How WCF works

    For readers who are not familiar with WCF, here we will give the most brief background information about what this technology is in practice. Context - two information systems, called client and service. The client at runtime accesses another information system that supports the service of interest to the client, and requires that some operation be performed. Then control is returned to the client.

    To create a service on WCF, you usually need to write an interface that describes the contract of the service being created, and a class that implements this interface. The class and interface are laid out with special WCF attributes, distinguishing them from other types, and specify some details of the behavior during the discovery and service invocation. These types are wrapped into something that works as a server (for example, in a DLL, which IIS is used for), and are supplemented with a configuration file (there are variants), where the details of the service implementation are indicated. Once started, the service can be accessed, for example, by a network address; In the Internet browser, you can see the contracts that the requested service implements.

    A programmer who wants to access an existing WCF service uses a console utility or a graphical interface built into the development environment to generate C # (or other supported language) types corresponding to the service contracts at the service’s existing address. The file with the obtained types is included in the project of the client application, and after that the programmer uses the same terms that are contained in the service interface, enjoying the benefits of progress (static typing). In addition, the client’s configuration file contains the technical characteristics of the called service (configuration is possible in the code, without a configuration file).

    WCF supports different modes of transport, encryption and other more subtle technical parameters. Most of them are united by the concept of "binding" (Binding). There are three important WCF service parameters:

    1. the address at which it is available;
    2. binding;
    3. contract (interfaces).

    All these parameters are set in the service and client configuration files.

    In our company, WCF (with and without distributed transactions) is widely used in embedded products, however, given the fashion trends, its use in new products is still under question.

    How to initiate distributed transactions in WCF

    To initiate transactions based on WCF System.Transactions, a programmer needs to place several attributes in the code, make sure that the bindings used support distributed transactions, are written on the client and in the service transactionFlow="true"and that a suitable transaction manager is running on all the computers involved. it will be MSDTC).

    Distributed transaction bindings: NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding, and WSFederationHttpBinding.

    The method (operation) of the service interface must be marked with an attribute System.ServiceModel.TransactionFlowAttribute. Then, with certain attribute parameters and when the TransactionScopeRequiredattribute parameter is set, the System.ServiceModel.OperationBehaviorAttributetransaction will be distributed between the client and the service. In addition, by default, the service is considered to vote for a transaction committed, unless an exception was thrown during execution. To change this behavior, you must set the corresponding TransactionAutoCompleteattribute parameter value System.ServiceModel.OperationBehaviorAttribute.

    Code of the simplest WCF service supporting distributed transactions.
        intDoSomething(string input);
    publicclassMyService : IMyService
        [System.ServiceModel.OperationBehavior(TransactionScopeRequired = true)]
        publicintDoSomething(string input)
            if (input == null)
                thrownew System.ArgumentNullException(nameof(input));
            return input.Length;

    Очевидно, что он отличается от кода обычной службы только использованием атрибута System.ServiceModel.TransactionFlow и специальной настройкой атрибута System.ServiceModel.OperationBehavior.

    Configuration example for this service.
            <service name="WcfWithTransactionsExample.MyService" behaviorConfiguration="serviceBehavior">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="mainWsBinding" contract="WcfWithTransactionsExample.IMyService"/>
                <endpoint address="mex" contract="IMetadataExchange" binding="mexHttpBinding"/>
                <binding name="mainWsBinding" maxReceivedMessageSize="209715200" maxBufferPoolSize="209715200" transactionFlow="true" closeTimeout="00:10:00"
                openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00">
                    <security mode="None"/>
                    <readerQuotas maxArrayLength="209715200" maxStringContentLength="209715200"/>

    Обратите внимание на то, что используется привязка типа WSHttpBinding и атрибут transactionFlow="true".

    TL; DR by section

    Distributed transactions include multiple resource managers, and all changes must either be committed or rolled back. Some modern DBMSs implement distributed transactions, which represent a convenient mechanism for connecting several databases. Software (not implemented in a DBMS) distributed transactions may include different combinations of resource managers on different computers running different operating systems, but they have limitations that need to be considered before relying on them. A modern alternative to distributed transactions are messaging-based solutions. In .NET Core, distributed transactions are not yet supported.

    WCF is one of the standard and proven tools for creating and accessing services in the .NET world, supporting several modes of transport and encryption. WCF is very close friends with distributed transactions based on System.Transactions. Configuring distributed transactions for WCF consists in marking the code with several attributes and adding a couple of words in the service and client configuration files. Not all WCF bindings support distributed transactions. In addition, obviously, transactions in WCF have the same limitations as without using WCF. The .NET Core platform so far only allows accessing services on WCF, and not creating them.

    Conclusion-Cheat Sheet

    This publication provides an overview of the fundamentals of .NET software transactions. Some conclusions regarding the trends in software transactions can be found in the sections on the applicability and limitations of the subjects in question, and in the conclusion the main theses of the publication are collected. I suppose that they can be used as a cheat sheet when considering software transactions as one of the options for implementing a technical system or for refreshing relevant information in memory.

    Transactions (domain, DBMS, software). The requirements of the subject area are sometimes formulated as transactions — operations that, starting in the field of holistic data, upon completion (including unsuccessful ones) should also come in the region of holistic data (perhaps already different). These requirements are usually implemented as system transactions. A classic example of a transaction is the transfer of money between two accounts, consisting of two indivisible operations - withdrawing money from one account and crediting to another. In addition to the well-known transactions implemented by the means of the DBMS, there are also software transactions, for example, in the .NET world. Resource managers are software components that are aware of the existence of such transactions and are able to be included in them, that is, to commit or roll back the changes made.System.Transactions.

    Durable and non-permanent resource managers. Durable resource managers support data recovery after a system failure. DBMS drivers for .NET usually offer this functionality. Non-permanent resource managers do not  support disaster recovery. Software transactional memory — a way to manage objects in RAM — can be viewed as an example of a non-persistent resource manager.

    Transactions and .NET resource managers. The .NET programmer uses software transactions and creates his own resource managers using types from the namespaceSystem.Transactions. This infrastructure allows the use of transactions of different nesting and isolation (with known limitations). The use of transactions is not difficult, and it consists in wrapping the code in a block usingwith certain characteristics. However, resource managers that are included in the transaction in this way must support the required functionality on their part. Using heterogeneous resource managers in a transaction or using one manager in different ways can automatically turn a transaction into a distributed one.

    Distributed transactions (DBMS, software).A distributed transaction covers several subsystems, changes in which must be synchronized, that is, they are either fixed together or rolled back. Distributed transactions are implemented in some modern DBMS. Software distributed transactions (these are not the ones that are implemented by the DBMS) impose additional restrictions on the interacting processes and platforms. Distributed transactions are gradually falling out of fashion, yielding to messaging services solutions. To turn a regular transaction into a distributed one, the programmer has little to do: when you include a resource manager with certain characteristics in the transaction, the transaction manager will automatically do everything necessary during the execution. Regular software transactions are available in .NET Core and .NET Standard, while distributed transactions are not available.

    Distributed transactions through WCF. WCF is one of the standard for .NET tools for creating and invoking services, which also supports standardized protocols. In other words, the specifically configured WCF services can be accessed from any application, not just .NET or Windows. To create a distributed transaction over WCF, you need to mark up the types that make up the service with additional attributes and make minimal changes to the service and client configuration files. In .NET Core and .NET Standard, you cannot create WCF services, but you can create WCF clients.

    An example for testing System.Transactions on GitHub


    Основные понятия

    ACID (русская «Википедия»)
    Алгоритм двухфазной фиксации (документация Microsoft)
    Однофазная фиксация (документация Microsoft)
    Программная транзакционная память (русская «Википедия»)
    Теорема Брюера (русская «Википедия»)
    Трехфазная фиксация (английская «Википедия»)

    Стандарты и конкретные реализации

    Стандарт распределенных транзакций X/Open XA (документ The Open Group)
    Java Transaction API (русская «Википедия»)
    Описание транзакций в объектной БД Cache (сайт компании InterSystems)

    Материалы о .NET

    Подробное описание транзакций .NET (сборник личных журналов Tech Blog Collection)
    Подробное руководство по работе с TransactionScope («Хабр»)
    Описание участника транзакции, поддерживающего продвижение и однофазную фиксацию (документация Microsoft)
    Что нового в .NET Standard 2.0 (репозиторий .NET Standard на GitHub)
    Проблемы с пулом соединений и уровнями изоляции транзакции (журнал YarFullStack)

    Материалы о микрослужбах

    Вводная статья по разработке транзакционных микрослужб («Хабр»)
    Принципы и примеры разработки транзакционных микрослужб («Хабр»)
    Обзорная статья по шаблону «Сага» и целостности данных в микрослужбах («Хабр»)
    Подробная статья о применении шаблона «Сага» с координатором («Хабр»)
    Ехидный комментарий о распределенных транзакциях («Хабр»)

    Editorial staff
    • 20.12.2018 Выпуск.
    • 21.12.2018 Исправления опечаток. Дополнения статьи по материалам комментариев OlegAxenow.
    • 23.12.2018 Дополнения статьи по материалам комментария qw1.

    Also popular now: