How to Write Atomic Transactions in .NET
 
Published: 18 Sep 2006
Abstract
Atomic transactions are the base part of ACID theory. Using this kind of transaction you can make sure your transaction will not cause any problem for the system if something goes wrong or if multiple requests are sent for the same resource or data. In this article Keyvan shows the simple process to write these transactions in .NET.
by Keyvan Nayyeri
Feedback
Average Rating: 
Views (Total / Last 10 Days): 37429/ 41

Introduction

Atomicity, Consistency, Isolation and Durability (ACID) theory is familiar to many programmers.  Those who work on Operating System design use it almost everday.

The most important aspect of ACID is an Atomic transaction.  What is an Atomic transaction?  The best way to describe it is a famous example: a bank accounting system.

Consider that bank A and bank B want to interact with someone's account at the same time.   Both banks want to withdraw from the account and the account has $10.00 in it.  If bank A takes $7.00 and at the same time bank B tries to get $5.00, what will happen?  When they start the transaction each bank believes there is $10.00 available in the account.  When one of them finishes the other one will find there is not enough money to finish the transaction.

This scenario is common for computer systems and you can see it many times in memory management, IO operations and database interactions.

Atomic transactions are a way to avoid this problem.  They simply lock on a transaction and do not allow any other transaction to interact with the resource.  If anything fails during the Atomic transaction, everything will return to the state before the transaction started.

In this article I give a step by step sample to write an Atomic transaction in .NET and test it to see it in action.  In this article I created a simple accounting system and then in an Atomic transaction I add values to an account and a place to throw exceptions for some arguments to see what would happen if something goes wrong in an Atomic transaction.

Prepare Database

To write my sample, I created a simple table with three columns:

·         id (int)

·         Name (navarchar)

·         Value (int)

Consider each row is an account in a bank.  Next, I created a stored procedure to add a value to a specific account.

Listing 1

CREATE PROCEDURE dbo.AddToAccount
    (
    @id int,
    @value int
    )
AS
    UPDATE dbo.MyTable
    SET Value = (Value + @value)
    WHERE id= @id

To finish preparing my database, I added a sample account to my table.

Figure 1

Write Atomic Transaction

Writing an Atomic transaction is easy in .NET.  First I created a Class Library project and then created a MyTransaction class to implement my logic in it.

MyTransaction has a Transfer() method that gets the identifier of an account, the integer value to add to that account and database connection string.  This method tries to add the value to the specific account in an Atomic transaction.  I produced an exception in this transaction to show what would happen if a value was not valid according to the sample constraints.  I threw this exception in after successfully adding a value to an account.

What I did to build my Class Library project was to add a reference to System.EnterpriseServices, inherited my class from ServicesComponent base class and called ContextUtil.SetComplete() method at the end of my Atomic transaction.

Figure 2

So initially my class looked like this:

Listing 2

using System;
using System.Collections.Generic;
using System.Text;
using System.EnterpriseServices;
using System.Data;
using System.Data.SqlClient; 
 
namespace AtomicTransactions
{
    public class MyTransaction  : ServicedComponent
    {

Note that I cannot provide a public default constructor for this class because later I cannot register it as COM+ component service.

Below is the logic for the Transaction() method.  After adding the value to database, I checked a constraint and threw the exception if the value was not between 0 and 10.  This is a sample constraint to show the result in test stages.

Listing 3

public void Transfer(int accountID, int value,
    string connectionString)
{
    try
    {
        AddValue(accountID, value, connectionString);
        if ((value < 0) || (value > 10))
            throw new 
                ArgumentException("Value isn't valid!");
 
        // Atomic transaction ends here
        ContextUtil.SetComplete();
    }
    catch (Exception ex)
    {
        throw new Exception
            ("An error occurred in our transaction");
    }
}

Simple logic for AddValue() method is:

Listing 4

private void AddValue(int id, int value, 
    string connectionString)
{
    try
    {
        using (SqlConnection connection =
            new SqlConnection(connectionString))
        {
            SqlCommand command = 
                new SqlCommand("AddToAccount", connection);
            command.CommandType = CommandType.StoredProcedure;
 
 
            command.Parameters.AddWithValue("id", id);
            command.Parameters.AddWithValue("value", value);
 
 
            connection.Open();
            command.ExecuteNonQuery();
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
Register Component Service

After creating my simple class, I needed to set its transaction behavior.  I can do this via an attribute in my code or via Component Services MMC snap-in.

There are several options for TransactionOption enumerator, but I chose RequiresNew to create a new transaction with my component.  All available options are:

·         Disabled: This does not use a current transaction, but also will not create a new one if it is not available.

·         NotSupported: The component will be created without any control available on it.

·         Required: This uses a current transaction or creates a new one if it is not available.

·         RequiresNew: This always creates a new transaction even if a current transaction is available.

·         Supported: This uses a current transaction, but will not create a new one if it is not available.

I also set my ApplicationAccessControl for assembly attribute to true.

Listing 5

namespace AtomicTransactions
{
    [assembly: ApplicationAccessControl(true)]
    [Transaction(TransactionOption.RequiresNew)]
    public class MyTransaction  : ServicedComponent
    {

Next, I built my assembly and made it COM Visible. I also created a strong name for this assembly.  Finally, I compiled my Class Library and registered it as a Component Service via Visual Studio command prompt:

C:\AtomicTransactions\bin\Debug>regsvcs AtomicTransactions.dll

Figure 3

Test Application

To test my Atomic transaction, I wrote a Windows Form application that used my Class Library.  It simply got the ID and value for an account and passed them to Transfer() method.  This Windows Form application must have a reference to System.EnterpriseServices as well.

Figure 4

This is the event handler for my btnTransfer button:

Listing 6

private void btnTransfer_Click(object sender, EventArgs e)
{
    MyTransaction transaction = new MyTransaction(); 
 
    try
    {
        transaction.Transfer(
            Convert.ToInt32(txtID.Text),
            Convert.ToInt32(txtValue.Text),
            "Data Source=localhost;Initial "
            + "Catalog=AtomicTransaction;" 
            + "Integrated Security=True;Pooling=False");
    }
    catch (Exception ex)
    {
        throw ex;
    } 
 
    MessageBox.Show("Finished!");
}
Test Results

Next, I ran my application to test my component.  Before doing this, I checked the current state of my database and Component Services Transaction Statistics:

Figure 5

Figure 6

I ran my application and gave valid values (1 for ID and 5 for value) to it.  The result was normal in the database, but look at the Transaction Statistics.

Figure 7

Figure 8

S now I provide an invalid value, say 11, for my application to throw an exception in my Atomic transaction.  Nothing will change in the database, but the Transaction Statistics show an Aborted transaction.  The normal form database must be updated because the value must be added to the database before exception.

Figure 9

Figure 10

Conclusion

You saw how easy it is to write an Atomic transaction in .NET.  Note that an Atomic transaction can be applied to many kinds of transactions not only database transactions.  For instance, you can apply it to files or use it to synchronize clustered servers.



User Comments

Title: ASAS   
Name: ASAS
Date: 2012-08-01 7:53:47 AM
Comment:
asd
Title: Very Gud   
Name: Soby
Date: 2007-06-09 7:26:03 AM
Comment:
This is very Help for me

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-18 7:57:16 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search