Saturday 1 March 2014

TransactionScope and SaveChanges in Entity Framework

TransactionScope class in .Net is great but if not used properly can cause table locks for long time and suffer application performance.

When using it with Entity Framework, only use TransactionScope when operation cannot be done within one SaveChanges() method or involves more than one data context.

Let's see the following codes. Imagine for some reasons, two data contexts are used.
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
    // some codes that do not involve database

    // some queries
	var student = contextOne.Students.Where( . . . );
	var schoolList = contextTwo.Schools;
	
	// more queries and validations
	//		check if student is allowed to move out ...
	//		check if student is allowed to move in ...
	
	// update student
	student.School = newSchool;
	
	// update some data in school context
	. . .
	
	
	contextOne.SaveChanges();
	contextTwo.SaveChanges();
	
    scope.Complete();
}
When we check SQL Profiler with tracing transactions enabled, we can see that Begin Transaction is executed immediately before the first database related operation. In this case is before the first data context querying a student (line #6). The transaction is wrapped up after the two data contexts are updated. This is a long time of locking and far beyond the necessary.

To enable tracing transactions, go to 'Events Selection' tab, click 'Show all events' then scroll to almost the end, expand 'Transactions' and tick the ones starting with 'TM: ...'

What should have been done is like the following:
    // some codes that do not involve database

    // some queries
	var student = contextOne.Students.Where( . . . );
	var schoolList = contextTwo.Schools;
	
	// more queries and validations
	//		check if student is allowed to move out ...
	//		check if student is allowed to move in ...
	
	// update student
	student.School = newSchool;
	
	// update some data in school context
	. . .
	

    using (var scope = new TransactionScope(TransactionScopeOption.Required))
    {	
	    contextOne.SaveChanges();
	    contextTwo.SaveChanges();
	
        scope.Complete();
    }
You can add try catch as well around the codes and discard the changes when there is an error.


Secondly, if there is only one data context needs to be updated, TransactionScope is not needed. Calling SaveChanges() method alone is enough and will create a transaction in database and execute any changes that have been made to the objects within the context.


For more information about TransactionScope, please see my previous article.

No comments: