The System. Transactions namespace is added to the. net 2.0 framework. A series of interfaces and classes are provided to facilitate the use of Transactions in. net 2.0. There are a lot of articles on operating database transactions in. net 2.0. Here we only mention how to design custom transaction operations.
I. Basic transaction usage
First look at the code for using transactions:
1 using (TransactionScope ts = new TransactionScope ())
2 {
3 // custom operation
4 ts. Complete ();
5}
Here, the using statement is used to define a piece of implicit transaction. If we add a piece of SQL Server operation code to the statement block, they will automatically join the transaction. It can be seen that the use of such transactions is extremely convenient.
So, is it possible to add our own transaction operations in this statement block, and this operation can be committed with the success of the entire transaction block and rolled back with its failure? The answer is certainly yes, otherwise I will not write this essay.
Ii. Implement custom transaction operations
Based on the features of the transaction, we can assume that this operation must have methods to implement actions such as commit and rollback. That's right. This is the IEnlistmentNotification interface in the System. Transactions namespace. Let's first write a simple implementation:
1 class SampleEnlistment1: IEnlistmentNotification
2 {
3 void IEnlistmentNotification. Commit (Enlistment enlistment)
4 {
5 Console. WriteLine ("Submit! ");
6 enlistment. Done ();
7}
8
9 void IEnlistmentNotification. InDoubt (Enlistment enlistment)
10 {
11 throw new Exception ("The method or operation is not implemented .");
12}
13
14 void IEnlistmentNotification. Prepare (PreparingEnlistment preparingEnlistment)
15 {
16 Console. WriteLine ("Prepare! ");
17 preparingEnlistment. Prepared ();
18}
19
20 void IEnlistmentNotification. Rollback (Enlistment enlistment)
21 {
22 Console. WriteLine ("rollback! ");
23 enlistment. Done ();
24}
25}
26
27
After the definition is complete, register with the Transaction Manager and add it to the current transaction:
1 using (TransactionScope ts = new TransactionScope ())
2 {
3 SampleEnlistment1 myEnlistment1 = new SampleEnlistment1 ();
4 Transaction. Current. EnlistVolatile (myEnlistment1, EnlistmentOptions. None );
5 ts. Complete ();
6}
After executing this code, we can get the following output:
Prepare!
Submit!
First, when the ts. Complete () method is called, the transaction has been successfully executed. Then, the transaction manager looks for all the currently registered entries, that is, every implementation of IEnlistmentNotification, and calls their Prepare method in sequence to notify each entry to Prepare for submission, when all entries call Prepared (), it means that they are ready, and then call their Commit method to submit them. If one of them calls ForceRollback instead of Prepared, the entire transaction will be rolled back. At this time, the transaction manager calls the Rollback method of each entry.
If we comment out the preceding ts. Complete () line, the execution result will become:
Roll back!
3. A custom operation for assigning values
Consider, we need to implement a transaction value assignment operation. What should I do? The following is an example:
1 class SampleEnlistment2: IEnlistmentNotification
2 {
3 public SampleEnlistment2 (AssignTransactionDemo var, int newValue)
4 {
5 _ var = var;
6 _ oldValue = var. I;
7 _ newValue = newValue;
8}
9
10 private AssignTransactionDemo _ var;
11 private int _ oldValue;
12 private int _ newValue;
13
14 void IEnlistmentNotification. Commit (Enlistment enlistment)
15 {
16 _ var. I = _ newValue;
17 Console. WriteLine ("Submit! The value of I is changed to: "+ _ var. I. ToString ());
18 enlistment. Done ();
19}
20
21 void IEnlistmentNotification. InDoubt (Enlistment enlistment)
22 {
23 throw new Exception ("The method or operation is not implemented .");
24}
25
26 void IEnlistmentNotification. Prepare (PreparingEnlistment preparingEnlistment)
27 {
28 preparingEnlistment. Prepared ();
29}
30
31 void IEnlistmentNotification. Rollback (Enlistment enlistment)
32 {
33 _ var. I = _ oldValue;
34 Console. WriteLine ("rollback! The value of I is changed to: "+ _ var. I. ToString ());
35 enlistment. Done ();
36}
37}
38
39 class AssignTransactionDemo
40 {
41 public int I;
42
43 public void AssignIntVarValue (int newValue)
44 {
45 SampleEnlistment2 myEnlistment2 = new SampleEnlistment2 (this, newValue );
46 Guid guid = new Guid ("{3456789A-7654-2345-ABCD-098765434567 }");
47 Transaction. Current. EnlistDurable (guid, myEnlistment2, EnlistmentOptions. None );
48}
49}
50
51
Then, use the following code:
1 AssignTransactionDemo atd = new AssignTransactionDemo ();
2atd. I = 0;
3 using (TransactionScope scope1 = new TransactionScope ())
4 {
5 atd. AssignIntVarValue (1 );
6 Console. WriteLine ("transaction completed! ");
7 scope1.Complete ();
8 Console. WriteLine ("before exiting the region, the I value is:" + atd. I. ToString ());
9}
10Thread. Sleep (1000 );
11Console. WriteLine ("after exiting the region, the I value is:" + atd. I. ToString ());
Run this code and we can see the following results:
Transaction completed!
Before exiting the region, the I value is 0.
Submit! The value of I changes to: 1.
After exiting the region, the I value is: 1
From the output result, the value assignment operation is successfully executed. Is it strange? Let's discuss it first:
1. If there is no Thread. Sleep (1000) line above, we will probably see that the I value will still be 0 in the output of the last line! Why? It's easy to understand. Here, the Commit method is called asynchronously, just like opening another thread. If the main thread does not wait, most of the transaction's Commit methods are not executed at the time of output, and the output results will certainly be incorrect.
2. In this example, the value assignment operation is actually executed in the Commit method. But in this example, we can also make an adjustment: Put the value assignment operation at the end of the AssignIntVarValue Method for execution, and then remove the value assignment operation from the Commit method. The related code changes are as follows:
1 class SampleEnlistment2: IEnlistmentNotification
2 {
3 void IEnlistmentNotification. Commit (Enlistment enlistment)
4 {
5 enlistment. Done ();
6}
7 // others
8}
9
10 class AssignTransactionDemo
11 {
12 public int I;
13
14 public void AssignIntVarValue (int newValue)
15 {
16 SampleEnlistment2 myEnlistment2 = new SampleEnlistment2 (this, newValue );
17 Guid guid = new Guid ("{3456789A-7654-2345-ABCD-098765434567 }");
18 Transaction. Current. EnlistDurable (guid, myEnlistment2, EnlistmentOptions. None );
19 I = newValue;
20 Console. WriteLine ("changed before submission! The value of I is: "+ I. ToString ());
21}
22}
23
24
In this way, the execution result will be changed:
Change before submission! I value: 1
Transaction completed!
Before exiting the region, the I value is: 1
After exiting the region, the I value is: 1
3. On the basis of the previous steps, make the following changes to the call to cause the transaction to fail:
1 using (TransactionScope scope1 = new TransactionScope ())
2 {
3 atd. AssignIntVarValue (1 );
4 Console. WriteLine ("transaction failed! ");
5 // scope1.Complete ();
6 Console. WriteLine ("before exiting the region, the I value is:" + atd. I. ToString ());
7}
The execution result will be changed:
Change before submission! I value: 1
Transaction Failed!
Before exiting the region, the I value is: 1
Roll back! The value of I is changed to: 0.
After exiting the region, the I value is 0.
The transaction is successfully rolled back.
Iv. Further Discussion
Previously, we only assigned values once. What if we need to perform the assignment twice?
1 using (TransactionScope scope1 = new TransactionScope ())
2 {
3 atd. AssignIntVarValue (1 );
4 atd. AssignIntVarValue (2 );
5 Console. WriteLine ("transaction failed! ");
6 // scope1.Complete ();
7 Console. WriteLine ("before exiting the region, the I value is:" + atd. I. ToString ());
8}
What will happen to the execution result? Of course we want to change the I value to 1 and then to 0 during rollback. But what about the actual results?
Change before submission! I value: 1
Change before submission! The I value is 2.
Transaction Failed!
Before exiting the region, the I value is 2.
Roll back! The value of I is changed to: 0.
Roll back! The value of I changes to: 1.
After exiting the region, the I value is: 1
Apparently, transaction rollback is not in the desired order. Why? After analyzing the mechanism, you can see that the transaction manager only sends an asynchronous call to each entry when sending a rollback command, and it is likely to issue the command in the registration order, the Calling sequence of the Rollback method is obviously not guaranteed.
At this time, if you make a small adjustment to the Rollback method:
1 void IEnlistmentNotification. Rollback (Enlistment enlistment)
2 {
3 while (_ var. I! = _ NewValue)
4 {
5 Thread. Sleep (500 );
6}
7 _ var. I = _ oldValue;
8 Console. WriteLine ("rollback! The value of I is changed to: "+ _ oldValue. ToString ());
9 enlistment. Done ();
10}
If you run it again, the result is correct:
Change before submission! I value: 1
Change before submission! The I value is 2.
Transaction Failed!
Before exiting the region, the I value is 2.
Roll back! The value of I changes to: 1.
Roll back! The value of I is changed to: 0.
The correct result is not in the correct call order, but the Rollback method first checks whether the value of _ newValue is consistent with the current value of I during execution, otherwise, wait for a while. While waiting, the Rollback method of another instance is executed, and the check result shows that it is matched, so it will roll back to 1. The first Rollback waits for the end of the check and finds a match, so it rolls back to 0.
Of course, in practice, this method is extremely undesirable. Not to mention that the execution sequence is still highly risky, but there is a big problem with the design method alone. What should we do in practical applications? Here we only provide the design idea, and the specific implementation code is not listed.
In the previous example, the two assignments are registered twice, which is the cause of instability. We should consider that the two assignments are still registered only once. During the first assignment, a SampleEnlistment2 instance is created and saved in AssignTransactDemo, and SampleEnlistment2 needs to record the current operation. This instance is still used for the next value assignment, and only operation records are allowed. In this way, the rollback operation can be performed according to the reverse sequence of the record.
What more? If there are multiple transactions that require value assignment, what should they do? In this case, we can add a Dictionary <Transaction, SampleEnlistment2> to the AssignTransactionDemo class. When using this function, we can find corresponding entries based on Transaction.
This article is currently discussed here. Among Microsoft's 101 Examples, there is an example of using transactions to copy files. There are in-depth implementations. If you have not read this article, we recommend that you study it. I believe you have read this article and it should not be a problem.