public void Save(T entity)
{
bool manageTransaction = ! orm.isActiveTransaction;
if (manageTransaction)
{
orm.BeginTransaction(IsolationLevel.ReadCommitted);
}
try
{
orm.session.SaveOrUpdate(entity);
if (manageTransaction)
{
orm.CommitTransaction();
}
}catch(Exception e)
{
if (manageTransaction)
{
orm.RollbackTransaction();
}
throw e;
}
}
Almost 90% of the code is for transaction management. It will be nice if we can write something like:
[Transaction(IsolationLevel.ReadCommitted)]
public void Save(T entity)
{
mOrm.session.SaveOrUpdate(entity);
mOrm.session.Flush();
}
To achieve this, I will use Spring.NET to intercept method call of the DAO. Before method marked with TransactionAttribute is executed a transaction will be started and then committed after method is executed. Although .NET provides solution for intercepting method calls via ContextBoundObject, I will use Spring.NET because this way my DAO object must implement just one interface instead of inheriting from ContextBoundObject.
Lets begin with the implementation. First I will declare the attribute used to mark transaction sensitive methods:
[AttributeUsage(AttributeTargets.Method)]
public class TransactionAttribute : Attribute
{
private IsolationLevel mIsolationLevel;
public TransactionAttribute()
{
mIsolationLevel = IsolationLevel.ReadCommitted;
}
public TransactionAttribute(IsolationLevel aIsolation)
{
mIsolationLevel = aIsolation;
}
public IsolationLevel isolationLevel
{
get { return mIsolationLevel; }
set { mIsolationLevel = value; }
}
}
Now I will decrare the class responsible for starting and committing transactions. There are a lot of specific issues about method interception and Spring.NET so if you are not familiar with them read the documentation about Spring.NET. The class looks like:
public class TransactionAroundAdvice : IMethodInterceptor
{
OrmManager mOrm;
public TransactionAroundAdvice()
{
mOrm = OrmManagerFactory.GetInstance();
}
private TransactionAttribute GetTransactionAttribute(MethodInfo aMethod)
{
object[] attributes = aMethod.GetCustomAttributes(
typeof(TransactionAttribute), true);
if (attributes.Length != 1)
{
//TODO: Throw more meaningfull exception and message
throw new Exception("Transaction Attribute is missing");
}
return (TransactionAttribute)attributes[0];
}
public object Invoke(IMethodInvocation invocation)
{
bool manageTransaction = ! mOrm.isActiveTransaction;
if (manageTransaction)
{
TransactionAttribute transactionAttribute =
GetTransactionAttribute(invocation.Method);
mOrm.BeginTransaction(transactionAttribute.isolationLevel);
}
//TODO: Find way to compare isolation level from TransactionAttribute and
//current active transaction if manageTransaction == false
object returnValue;
try
{
returnValue = invocation.Proceed();
if (manageTransaction)
{
mOrm.CommitTransaction();
}
}catch(Exception e)
{
if (manageTransaction)
{
mOrm.RollbackTransaction();
}
if ((e is TargetInvocationException) && (e.InnerException != null))
{
//Because when AOP is enables, method is invoked via reflection
//TargetInvocationException is thrown. This exception is most likely
//to confuse application developer, so it is removed
throw e.InnerException;
}
else
{
throw e;
}
}
return returnValue;
}
}
When a method marked with TransactionAttribute is intercepted then Invoke method from TransactionAroundAdvice class is called. Now lets see how our DAO class looks like when transaction management code is removed:
public class AttributeEnabledDAO<T>: IDAO<T>
{
public AttributeEnabledDAO()
{
mOrm = OrmManagerFactory.GetInstance();
}
private OrmManager mOrm;
#region IDAO Members
public T Load(object id)
{
return (T) mOrm.session.Load(typeof(T), id);
}
[Transaction(IsolationLevel.ReadCommitted)]
public void Save(T entity)
{
mOrm.session.SaveOrUpdate(entity);
mOrm.session.Flush();
}
#endregion
}
Much more simple. Example of using AttributeEnabledDAO is given bellow:
ProxyFactory articleDAOFactory = new ProxyFactory(
new AttributeEnabledDAO<Article>());
articleDAOFactory.AddAdvisor(new DefaultPointcutAdvisor(
new AttributeMatchMethodPointcut(typeof(TransactionAttribute)),
new TransactionAroundAdvice()));
IDAO<Article> articleDao;
lock (synchObject)
{
articleDao = (IDAO<Article>) articleDAOFactory.GetProxy();
}
Article article = articleDao.Load(1);
article.Name = "Paracetamol";
article.SalePrice = rnd.NextDouble();
articleDao.Save(article);
No comments:
Post a Comment