Now: Declarative Database Transactions With ServicedComponent
Developers working with Microsoft SQL Server enjoyed declarative handling of transactions since Windows NT 4.0 days. With the .NET Framework, this usually means we need to inherit a class from ServicedComponent
, apply TransactionAttribute
to it, and use SetAbort
/SetComplete
methods to notify the framework if our DB operation succeeded or failed. The framework takes care of the rest.
Simple? Yes, but�
ServicedComponent
requires you to sign your assemblies, the step considered by many developers as the unnecessary complication.
- You can�t mix transactional and non-transactional methods in one class, which forces you to split a single logical
CustomerService
into CustomerReader
and CustomerWriter
or something like that.
Future: TransactionScope in .NET 2.0
In .NET 2.0, we are going to have the TransactionScope
class which solves both problems. Used as braces around a code which must run inside a database transaction and, although not the declarative style anymore, it provides a nice clean model:
using (TransactionScope scope = new TransactionScope())
{
�
Scope.Complete();
}
The Future's Here
OK, the code above looks as good as I�d like it to be, but Whidbey isn�t coming to production machines near me for another year or so. I had to create my own solution using .NET 1.1, and here it is. In fact, the implementation is so embarrassingly simple that I now feel ashamed I didn�t figure it out earlier!
Usage:
using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(connString))
using (SqlCommand cmd = new SqlCommand(updateSql, conn))
{
System.Diagnostics.Debug.Assert(
System.EnterpriseServices.ContextUtil.IsInTransaction);
conn.Open();
int result = cmd.ExecuteNonQuery();
if (result == 1)
{
scope.Complete();
}
}
Here�s the complete class:
using System;
using System.EnterpriseServices;
namespace Omnitalented.EnterpriseServices
{
public class TransactionScope : IDisposable
{
private bool succeded;
public TransactionScope() : this(TransactionOption.Required,
TransactionIsolationLevel.Any, 60)
{
}
public TransactionScope(TransactionOption transactionOption) :
this(transactionOption, TransactionIsolationLevel.Any, 60)
{
}
public TransactionScope(TransactionOption transactionOption,
TransactionIsolationLevel isolationLevel, int timeoutSeconds)
{
ServiceConfig cfg = new ServiceConfig();
cfg.Transaction = transactionOption;
cfg.IsolationLevel = isolationLevel;
cfg.TransactionTimeout = timeoutSeconds;
ServiceDomain.Enter(cfg);
}
public void Complete()
{
succeded = true;
}
public void Dispose()
{
if (succeded)
{
ContextUtil.SetComplete();
}
else
{
ContextUtil.SetAbort();
}
ServiceDomain.Leave();
}
}
}
Please note: The code only works in Windows 2003 Server or Windows XP; this is a requirement for the System.EnterpriseServices.ServiceDomain
class.