Transaction In .NET

Introduction
 
Most applications deal with back-end databases, and we need the data to be atomic, which means no anomalies in the data. For instance, you're supposed to insert data into master and child tables in the SQL Server. During the above task if you inserted one row into the master table and then some error occurred then what would happen to that task? Because of the error, child data is not inserted and you must rollback the master table data also; otherwise data inconsistency will happen. Here transaction plays a vital role to make sure data operations that occur happen successfully.
 
What is transaction?
 
A transaction is a single unit of work which means either ALL or NONE. If a transaction is successful, all of the data operations are committed and become a durable part of the database. If a transaction encounters errors/exceptions and must be canceled or rolled back, then all of the data modifications/operations need to be removed.
 
Example: Transfer money from Bank Account1 to Account2. It consists of two processes: Withdraw amount from Account1 and Deposit that amount to Account2.Transferring money will only be accurate and successful if both the processes are individually successful. If that is not happening, suppose process 1 succeeds but process 2 fails, then the money will be deducted from Account 1 but not deposited to Account 2. If that happens, it will be very bad and no one will accept it.
 
Properties of Transaction
 
Transaction has the following four standard properties, usually call by the acronym ACID.
  • Atomicity
    Ensures that all operations within the work unit are completed successfully; If the transaction fails then operations are rolled back to their previous state.

  • Consistency
    Ensures that the database properly changes states upon a successfully committed transaction.

  • Isolation
    Enables transactions to operate independently of and transparent to each other.

  • Durability
    Ensures that a committed transaction persists in case of a system failure.


    Figure 1: ACID properties 
Using Code
 
We will discuss how we can implement transaction in ADO.NET, EntityFramework and SQLServer.
 
For demonstration, we create two tables (ProjectMember, Project). In ProjectMember table, column ProjectID is a foreign key to Project table's ProejctID column.
  1. -- Table1
  2. CREATE TABLE tblProject  
  3. (  
  4.    ProjectID int PRIMARY KEY,  
  5.    Name varchar(50) NULL  
  6. );  
  7.   
  8. --Table2
  9. CREATE TABLE tblProjectMember  
  10. (  
  11.    MemberID int,  
  12.    ProjectID int foreign Key references Project(ProjectID)  
  13. );  
In ADO.NET
 
Often we use ADO.NET to perform Database opertaions. Here is the sample code to implement transaction in ADO.NET.
  1. string strConnString = "myconnectionstring"// get it from Web.config file  
  2. SqlTransaction objTrans = null;  

  3. using (SqlConnection objConn = new SqlConnection(strConnString))  
  4. {  
  5.    objConn.Open();  
  6.    objTrans = objConn.BeginTransaction();  
  7.    SqlCommand objCmd1 = new SqlCommand("insert into tblProject values(1, 'TestProject')", objConn);  
  8.    SqlCommand objCmd2 = new SqlCommand("insert into tblProjectMember(MemberID, ProjectID) values(2, 1)", objConn);  
  9.    try  
  10.    {  
  11.       objCmd1.ExecuteNonQuery();  
  12.       objCmd2.ExecuteNonQuery(); // Throws exception due to foreign key constraint 

  13.       objTrans.Commit();  
  14.    }  
  15.    catch (Exception)  
  16.    {  
  17.       objTrans.Rollback();  
  18.    }  
  19.    finally  
  20.    {  
  21.       objConn.Close();  
  22.    }  
  23. }  
In the above code sample, it executes two SQL queries: first it inserts record to project, second it inserts record to projectMember table. First SQL statement executes properly but second sql statement throws exception because we are inserting projectId as 2 which is not present in project table. Transaction dictates either complete success or failure, there is nothing in between. So to avoid the above problem, we will use Transaction to make everything perfect. Here it uses SqlTransaction class and creates object by calling BeginTransaction() method of SqlConnection class. Transaction starts here. If everything goes fine then commit (save into database) it otherwise rollback (delete inserted record in scope in the transaction) it.
 
We can achieve Transaction implementation using TransactionScope class.
 
Using TransactionScope
 
It makes code block in transactional mode. It cannot be inherited. It is present in namespace System.Transactions.TransactionScope
 
It has 3 main properties(IsolationLevel.
  • Isolation Level
    It defines the locking mechanism to read data in another transaction. Available options are Read UnCommitted, Read Committed, Repeatable Read, Serializable. Default is Serializable.

  • Timeout
    How much time transaction object will wait to be completed. SqlCommand Timeout is different than Transaction Timeout. SqlCommand Timeout defines how much time the SqlCommand object will wait for a database operation to be completed. Available options are 10 minutes. Default is 1 minute.

  • TransactionScopeOption
    It is an enum. Following are the options. 

Option

Description

Disable

This component does not participate in a transaction. This is the default value.

NotSupported

This component runs outside the context of a transaction.

Required

It is the default value for TransactionScope. If any already exists then it will join with that transaction otherwise create new one.

RequiresNew

When this option is selected a new transaction is always created. This transaction is independent with its outer transaction.

Suppress

When this option is selected, no transaction will be created. Even if is already  there.

 
You can set default time out in web.config like.
  1. <system.transactions>  
  2.   <defaultSettings timeout="30"/>  
  3.   <machineSettings maxTimeout="1200"/>  
  4. </system.transactions>  
 System.Transactions is available by default; we need to add the reference. See Figure 2 for adding the reference.
 

Figure 2: Add Transaction assembly reference 
 
In the below code sample, it creates TransactionScope class object and defines SQL queries to add records to Project table, ProjectMember table. Once everything is fine, it calls Complete() to commit the data. If exception occurs it rollsback to previous state.
  1. using (var txscope =new TransactionScope(TransactionScopeOption.RequiresNew))    
  2. {    
  3.    try    
  4.    {    
  5.       using (SqlConnection objConn = new SqlConnection(strConnString))    
  6.       {    
  7.             objConn.Open();    
  8.             SqlCommand objCmd1 = new SqlCommand("insert into tblProject values(1, 'TestProject')", objConn);    
  9.             SqlCommand objCmd2 = new SqlCommand("insert into tblProjectMember(MemberID, ProjectID) values(2, 1)", objConn);    
  10.   
  11.             objCmd1.ExecuteNonQuery();    
  12.             objCmd2.ExecuteNonQuery(); // Throws exception due to foreign key constraint  
  13.   
  14.             //The Transaction will be completed    
  15.             txscope.Complete();               
  16.       }         
  17.    }    
  18.    catch(Exception ex)    
  19.    {    
  20.       // Log error    
  21.       txscope.Dispose();    
  22.    }    

What is the difference between TransactionScope and BeginTranaction()??
  • TransactionScope is usually a better choice because it allows you to nest calls to other methods that might require a transaction without you having to pass the transaction state around.
  • With TransactionScope, as long as the scope exists, it will handle everything that registers with the current Transaction on the thread, making your code cleaner, and more maintainable.
  • TransactionScope uses MS DTC(Microsoft Distributed Transaction Coordinator) for transaction management.
  • Due to its ease of use and efficiency, it is recommended that you use the TransactionScope class when developing a transaction application.  
In EntityFramework
 
In  EntityFramework(EF) we have different versions, so EF 6.0 provides two APIs which are not available in previous versions. So we will discuss how can we implement transaction in earlier versions of EF. We can achieve it through TransactionScope class. In below code sample, it creates TransactionScope class object and defines EF code for adding object to Project table, ProjectMember table. Once everything is fine, it calls Complete() to commit the data. If exception occurs it rollsback to previous state.
  1. using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))  
  2. {  
  3.     try  
  4.     {  
  5.         // In the Data Access Layer  
  6.         using (DBContextEntitis entities = new DBContextEntitis(entitiesConnectionstring))  
  7.         {  
  8.             Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
  9.             context.Project.Add(proj);  
  10.               
  11.             ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
  12.             context.ProjectMembers.Add(projMember);  
  13.               
  14.             //The Transaction will be completed  
  15.             scope.Complete();  
  16.         }  
  17.     }  
  18.     catch(Exception ex)  
  19.     {  
  20.         scope.Dispose();  
  21.     }  

Transaction in EntityFramework 6.0
 
By default EntityFramework Inserts, Updates or Deletes operations in a transaction, when you call SaveChanges() method of context object. EF starts a new transaction for each operation and completes the transaction when the operation finishes. EntityFramework 6.0 provides two new methods.
 
Database.BeginTransaction()
 
It facilitates starting and completing transactions themselves within an existing DbContext object. It allows several operations to be combined within the same transaction and hence either all are committed or all are rolled back as one. You can specify more easily the isolation level for the transaction. 
  1. using (var context = new MyDBContext())  
  2. {  
  3.    using (System.Data.Entity.DbContextTransaction dbTran = context.Database.BeginTransaction( ))  
  4.    {  
  5.       try  
  6.       {  
  7.          Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
  8.          context.Project.Add(proj); 

  9.          ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
  10.          context.ProjectMembers.Add(projMember); 

  11.          //saves all above operations within one transaction  
  12.          context.SaveChanges(); 

  13.          //commit transaction  
  14.          dbTran.Commit();  
  15.       }  
  16.       catch (Exception ex)  
  17.       {  
  18.          //Rollback transaction if exception occurs  
  19.          dbTran.Rollback();  
  20.       }  
  21.    }  
  22. }  
In above code, exception will occur due to wrong project ID of ProjectMember object. Transaction object dbTran will rollback all operations that  happened before. Now you wont get any record in Project table as well ProjectMember table.
 
Database.UseTransaction() 

It allows the DbContext to use a transaction which was started outside of the Entity Framework.
  1. using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))  
  2. {  
  3.    try  
  4.    {  
  5.       // In the Data Access Layer  
  6.       using (DBContextEntitis entities = new DBContextEntitis(entitiesConnectionstring))  
  7.       {  
  8.          Project proj = new Project() { ProjectID = 1, Name = "Temp Project" };  
  9.          context.Project.Add(proj);  
  10.          ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
  11.          context.ProjectMembers.Add(projMember);  
  12.          //The Transaction will be completed  
  13.          scope.Complete();  
  14.       }  
  15.    }  
  16.    catch(Exception ex)  
  17.    {  
  18.       scope.Dispose();  
  19.    }  
  20. }  
To implement UseTransaction, you need to add constructor which will receive Database connection and closing connection indicator. The contextOwnConnection informs the Entity Framework to not close the connection when it is done.
  1. public class DBEntitiesContext : DbContext  
  2. {  
  3.    public EntitiesContext() : base("name=Entities")  
  4.    {  
  5.       Database.Log = Console.WriteLine;  
  6.    }

  7.    public EntitiesContext(DbConnection existingConnection, bool contextOwnConnection)  
  8.    : base(existingDBConnection, contextOwnConnection)  
  9.    {  
  10.    }  
  11. }  
In below code snippet it creates transaction object using BeginTransaction() method. Inside transaction object scope, it executes some SQL queries. Next it creates EntityFramework and creates object by passing two params, SqlConnection object and closing connection indicator. It calls UseTransaction() with passing transaction object which created before. Now entityframework operations are appended to previous transaction scope.
  1. using(SqlConnection con = new SqlConnection("connectionString"))  
  2. {  
  3.    con.Open(); 

  4.    using(var transaction = con.BeginTransaction())  
  5.    {  
  6.       SqlCommand objCmd1 = new SqlCommand("insert into tblProject values(1, 'TestProject')", objConn);  
  7.       objCmd1.ExecuteNonQuery(); 

  8.       //Pass this transaction to EF....  
  9.       using (DBEntitiesContext context = new DBEntitiesContext(con, false))  
  10.       {  
  11.          context.Database.UseTransaction(transaction);  
  12.          ProjectMember projMember = new ProjectMember() { ProjectID = 2, MemberID = 1 };  
  13.          context.ProjectMembers.Add(projMember); 

  14.          context.SaveChanges();  
  15.       }  
  16.    }  
  17. }  
Default transaction level in EF6 has changed to READ COMMITTED SNAPSHOT
 
In SQL Server
 
We implemented transaction in front-end code(C# code) but we can also define transaction in back-end code like SQLServer database.
 
Transaction Control:
 
There are the following commands used to control transactions.
  • COMMIT To save the changes.
  • ROLLBACK To rollback the changes.
  • SAVEPOINT Creates points within groups of transactions in which to ROLLBACK
  • SET TRANSACTION Places a name on a transaction. 
Types of Transactions:
 
Implicit transactions are maintained by SQL Server for each and every DDL (CREATE, ALTER, DROP, TRUNCATE), DML (INSERT, UPDATE, DELETE) statements. All these T-SQL statements run under the implicit transaction. If an error occurs within these statements individually, SQL Server will roll back the complete statement.
 
Explicit transactions are defined by programmers. In Explicit transaction we include the DML statements that need to be executed as a unit. Since SELECT statements don't modify data, hence generally we don’t include Select statement in a transaction.
 
Isolation level
 
It controls the locking and row versioning behavior of Transact-SQL statements issued by a connection to SQL Server. There are five types of Isolation levels.
  • READ UNCOMMITTED
    If any table is updated(insert or update or delete) under a transaction and the same transaction is not completed that is not committed or rolled back then uncommitted values will display (Dirty Read) in select query of "Read Uncommitted" isolation transaction sessions.

  • READ COMMITTED
    In select query it takes only committed values of table. If any transaction is opened and incomplete on table in other sessions then select query will wait until no transactions are pending on same table. 

  • REPEATABLE READ
    Select query data of table that is used under transaction of isolation level. "Repeatable Read" cannot be modified from any other sessions until transaction is completed. 

  • SERIALIZABLE
    Serializable Isolation is similar to Repeatable Read Isolation but the difference is it prevents Phantom Read. This works based on range lock.

  • SNAPSHOT
    Snapshot isolation is similar to Serializable isolation. The difference is Snapshot does not hold lock on table during the transaction so table can be modified in other sessions. Snapshot isolation maintains versioning in Tempdb for old data in case any data modification occurs in other sessions, then existing transaction displays the old data from Tempdb. It is highest level of Isolation but it affects performance. 
In SQLServer it is READ COMMITTED. For more on isolation level, visit here.
 
Now we create StoredProcedure(SP) which implements transaction. The SP executes two inserted SQL statements: one for tblProject table another is tblProjectMember table. It keeps all SQL statements inside BEGIN TRANSACTION block and then commits. If any SQL fails then it goes to CATCH block and rollsback to previous state of database.
  1.    
  2. CREATE PROCEDURE spAddProject  
  3.    @ProjectID INT,  
  4.    @MemberID INT,  
  5.    @Name VARCHAR(10)  
  6. AS  
  7. BEGIN  
  8.    BEGIN TRY  
  9.       BEGIN TRANSACTION;  
  10.          -- Insert record into Project table  
  11.          INSERT INTO tblProject(ProjectID, NameVALUES(1, 'TestProject'); 

  12.          -- Insert record into ProjectMember table  
  13.          INSERT INTO tblProjectMember(MemberID, ProjectID) VALUES(2, 1); 

  14.          COMMIT TRANSACTION;  
  15.    END TRY  
  16.    BEGIN CATCH  
  17.       IF @@TRANCOUNT > 0  
  18.          ROLLBACK TRANSACTION;
  19.  
  20.       DECLARE @ErrorNumber INT = ERROR_NUMBER();  
  21.       DECLARE @ErrorLine INT = ERROR_LINE();  
  22.       DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
  23.  
  24.       PRINT 'Actual error number: ' + CAST(@ErrorNumber AS VARCHAR(10));  
  25.       PRINT 'Actual line number: ' + CAST(@ErrorLine AS VARCHAR(10)); 

  26.       RAISERROR(@ErrorMessage);  
  27.    END CATCH  
  28. END;  
@@TRANCOUNT: Returns the number of BEGIN TRANSACTION statements that have occurred on the current transaction. The BEGIN TRANSACTION statement adds @@TRANCOUNT by 1. ROLLBACK TRANSACTION l @@TRANCOUNT to 0. COMMIT TRANSACTION or COMMIT WORK subtracts @@TRANCOUNT by 1.
 
Let's call the Stored procedure from code like below.
  1. using (SqlConnection con = new SqlConnection(connectionString))  
  2. {  
  3.    using (SqlCommand cmd = new SqlCommand("spAddProject", con))  
  4.    {  
  5.       cmd.CommandType = CommandType.StoredProcedure;  
  6.       cmd.Parameters.AddWithValue("@ProjectID", 1);  
  7.       cmd.Parameters.AddWithValue("@MemberID", 1);  
  8.       cmd.Parameters.AddWithValue("@Name""Test Proejct"); 

  9.       con.Open();  
  10.       cmd.ExecuteNonQuery();  
  11.    }  
  12. }  
Distributed Transactions
 
A transaction which works with multiple data sources is called a distributed transaction. If a transaction fails then the affected data sources will be rolled back. In System.Transactions, MSDTC (Microsoft Distributed Transaction Coordinator) manages distributed transactions. It implements a two phase commit protocol. A distributed transaction is much slower than a local transaction. The transaction object automatically escalates a local transaction to a distributed transaction when it understands that a distributed transaction is needed. The developer cannot do anything here.
 
Conclusion
 
We discussed about what is transaction and how we can use in .Net, SQLServer. We also discussed transaction in ADO.NET, EntityFramework. Choose as per your requirement.
 
Hope this helps.

Up Next
    Ebook Download
    View all
    Learn
    View all