Click here to Skip to main content
15,892,059 members
Articles / Programming Languages / C#

Declarative Transactions using ADO.NET and without Enterprise Services

Rate me:
Please Sign up or sign in to vote.
4.95/5 (20 votes)
26 Oct 20018 min read 216.7K   1.5K   87  
Sometimes, it is nice to prototype up a simple database application. This code may help, by providing the automatic transactional model of COM+ in a non-COM+ environment. This example uses "Interception" to provide automatic transactioning support for non-COM+ classes.
namespace RSI.Transactions
{
	using System;
	using System.Threading;
	using System.Runtime.Remoting;
	using System.Runtime.Remoting.Contexts;
	using System.Runtime.Remoting.Activation;
	using System.Runtime.Remoting.Messaging;
	using System.Data.SqlClient;
	using System.Data;

	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	public class ContextUtil
	{
		static public void		SetAbort()
		{
			TransactionAttribute Property = Transaction.ContextProperty;
			if(Property == null)
				return;

			Property.SetAbort();
		}

		static public void		SetComplete()
		{
			TransactionAttribute Property = Transaction.ContextProperty;
			if(Property == null)
				return;

			Property.SetComplete();
		}

		static public IDbTransaction DbTransaction
		{
			get 
			{
				TransactionAttribute Property = Transaction.ContextProperty;
				if(Property == null)
					return null;

				return Property.DbTransaction;
			}
		}

	}

	public class DbConnectionUtil
	{
		static public IDbConnection Connection
		{
			get {  return DbConnectionAttribute.Connection; }
		}
	}

	[AttributeUsage(AttributeTargets.Method)]
	public class NoDbConnectionAttribute : Attribute
	{
	}

	[AttributeUsage(AttributeTargets.Class)]
	public class DbConnectionAttribute : ContextAttribute, IContributeObjectSink
	{
		private SqlConnection	m_SqlConnection;

		public					DbConnectionAttribute()
			: base(PropertyName)
		{
			m_SqlConnection = null;
		}

		private static String	PropertyName 
		{
			get { return "WorkSight.DataAccess.Connection"; }
		} 

		internal static DbConnectionAttribute CurrentConnection
		{
			get { return Thread.CurrentContext.GetProperty(PropertyName) as DbConnectionAttribute; }
		}

		internal void			OpenConnection()
		{
			if(m_SqlConnection == null)
			{
#if DEBUGGING_TRXS
				Console.WriteLine("[" + Thread.CurrentContext.ContextID + "]" + " Opening Database Connection...");
#endif
				//
				// TODO: Put your Datasource Connection Here!
				//
				m_SqlConnection = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["dsn"]);
				m_SqlConnection.Open();
			}
		}

		internal void			CloseConnection()
		{
			if(m_SqlConnection != null)
			{
#if DEBUGGING_TRXS
				Console.WriteLine("[" + Thread.CurrentContext.ContextID + "]" + " Closing Database Connection...");
#endif
				m_SqlConnection.Close();
				m_SqlConnection.Dispose();
				m_SqlConnection = null;
			}
		}

		static internal IDbConnection Connection
		{
			get 
			{ 
				DbConnectionAttribute currentConnection = CurrentConnection;
				if(currentConnection != null)
					return currentConnection.m_SqlConnection; 
				return null;
			}
		}

		public override bool	IsContextOK(Context ctx, IConstructionCallMessage ctor)
		{
			return true;
		}

		public override	void	GetPropertiesForNewContext(IConstructionCallMessage ctor)
		{
			ctor.ContextProperties.Add(this);
		} 

		public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink m_Next)
		{
			TransactionAttribute transactionProperty = Transaction.ContextProperty;
			if(transactionProperty != null)
			{
				return new DbConnectionMessageSink(this, new TransactionMessageSink(transactionProperty, m_Next));
			}

			return new DbConnectionMessageSink(this, m_Next);
		}

	}
	
	public class DbConnectionMessageSink : IMessageSink
	{
		private IMessageSink			m_Next;
		private DbConnectionAttribute	m_DbConnectionAttribute;

		internal DbConnectionMessageSink(DbConnectionAttribute connectionAttribute, IMessageSink ims)
		{
			m_Next					= ims;
			m_DbConnectionAttribute	= connectionAttribute;
		} 

		public IMessageSink NextSink 
		{
			get { return m_Next; }
		}

		public IMessage SyncProcessMessage(IMessage imCall)
		{
			if (!(imCall is IMethodMessage))
				return m_Next.SyncProcessMessage(imCall);

			IMethodMessage imm = imCall as IMethodMessage;
			bool bNoConnection = (Attribute.GetCustomAttribute(imm.MethodBase, typeof(NoDbConnectionAttribute)) != null);
			if(bNoConnection)
				return m_Next.SyncProcessMessage(imCall);

			m_DbConnectionAttribute.OpenConnection();
			IMessage imReturn = m_Next.SyncProcessMessage(imCall);
			m_DbConnectionAttribute.CloseConnection();
			return imReturn;
		} 

		public IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)
		{
			// TODO: Find some way to also allow AsyncMessages to work (and ideally, be tracked)
			return m_Next.AsyncProcessMessage(im, ims);
		} 
	}
	
	public enum TransactionOption 
	{ 
		Disabled = 0,
		NotSupported,
		Required,
		RequiresNew,
		Supported
	}

	public class Transaction
	{
		internal static String PropertyName 
		{
			get { return "WorkSight.DataAccess.Transaction"; }
		} 
	
		internal static TransactionAttribute ContextProperty 
		{
			get { return Thread.CurrentContext.GetProperty(Transaction.PropertyName) as TransactionAttribute; }
		} 
	} 

	[AttributeUsage(AttributeTargets.Method)]
	public class AutoCompleteAttribute : Attribute
	{
	}

	[AttributeUsage(AttributeTargets.Class)]
	public class TransactionAttribute : ContextAttribute
	{
		private TransactionOption	m_TransactionOption;
		private bool				m_bDone					= false;
		private bool				m_bContextConsistent	= false;
		private SqlTransaction		m_SqlTransaction		= null;


		public TransactionAttribute(TransactionOption m_TransactionOption)
			: base(Transaction.PropertyName)
		{
			this.m_TransactionOption = m_TransactionOption;
		}

		//Define Name property.
		//This is a read-only attribute.
		public TransactionOption TransactionContext
		{
			get {return this.m_TransactionOption;}      
		}

		public override bool IsContextOK(Context ctx, IConstructionCallMessage ctor)
		{
			if(m_TransactionOption == TransactionOption.RequiresNew)
				return false;

			TransactionAttribute transactionProperty = ctx.GetProperty(Transaction.PropertyName) as TransactionAttribute;
			if(m_TransactionOption == TransactionOption.Required)
			{
				if(transactionProperty == null)
					return false;
			}
			return true;
			/*
						if(m_TransactionOption == TransactionOption.NotSupported)
							return true;

						if(m_TransactionOption == TransactionOption.Disabled)
							return true;

						if(m_TransactionOption == TransactionOption.Supported)
							return true;

						return true;
			*/
		}

		public override void GetPropertiesForNewContext(IConstructionCallMessage ctor)
		{
			ctor.ContextProperties.Add(this);
		} 

		public bool Done
		{
			get { return m_bDone; }
		}

		public bool ContextConsistent
		{
			get { return m_bContextConsistent; }
		}

		public void SetAbort() 
		{
			m_bContextConsistent = false;
			m_bDone = true;
		}

		public void EnableCommit() 
		{
			if(m_bDone)
				return;

			m_bContextConsistent = true;
		}
		
		public void DisableCommit() 
		{
			if(m_bDone)
				return;

			m_bContextConsistent = false;
		}
		
		public void SetComplete() 
		{
			if(m_bDone)
				return;

			m_bContextConsistent = true;
			m_bDone = true;
		}

		public SqlTransaction DbTransaction 
		{
			get { return m_SqlTransaction; }
			set { m_SqlTransaction = value; }
		}
	}

	public class TransactionMessageSink : IMessageSink
	{
		private IMessageSink			m_Next;
		private TransactionAttribute		m_TransactionAttribute;

		internal TransactionMessageSink(TransactionAttribute transactionProperty, IMessageSink ims)
		{
			m_Next					= ims;
			m_TransactionAttribute	= transactionProperty;
		} 

		public IMessageSink NextSink 
		{
			get { return m_Next; }
		}

		public IMessage SyncProcessMessage(IMessage imCall)
		{
			// Perform whatever preprocessing is needed on the message
			if (!(imCall is IMethodMessage))
				return m_Next.SyncProcessMessage(imCall);

			IMethodMessage imm = imCall as IMethodMessage;
			bool bAutoComplete = (Attribute.GetCustomAttribute(imm.MethodBase, typeof(AutoCompleteAttribute)) != null);
			m_TransactionAttribute.DisableCommit();

			SqlConnection Connection = (SqlConnection)DbConnectionAttribute.Connection;
			if(Connection == null)
				return m_Next.SyncProcessMessage(imCall);

#if DEBUGGING_TRXS
				Console.WriteLine("[" + Thread.CurrentContext.ContextID + "]" + " Beginning Transaction...");
#endif
			SqlTransaction dbTransaction = m_TransactionAttribute.DbTransaction = Connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);

			// Dispatch the call on the object
			IMessage imReturn = m_Next.SyncProcessMessage(imCall);

			if(dbTransaction != null)
			{
				IMethodReturnMessage methodReturn = imReturn as IMethodReturnMessage;
				Exception exc = methodReturn.Exception;
				if (exc != null)
				{
					m_TransactionAttribute.SetAbort();
				}
				else
				{
					if(bAutoComplete)
						m_TransactionAttribute.SetComplete();
				}

				if(!m_TransactionAttribute.Done)
					m_TransactionAttribute.SetAbort();

				if(m_TransactionAttribute.ContextConsistent)
				{
#if DEBUGGING_TRXS
					Console.WriteLine("[" + Thread.CurrentContext.ContextID + "]" + " Commiting Transaction...");
#endif
					dbTransaction.Commit();
				}
				else
				{
#if DEBUGGING_TRXS
					Console.WriteLine("[" + Thread.CurrentContext.ContextID + "]" + " Aborting Transaction...");
#endif
					dbTransaction.Rollback();
				}

				dbTransaction.Dispose();
				dbTransaction = null;
			}

			return imReturn;
		} 

		public IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)
		{
			// TODO: Find some way to also allow AsyncMessages to work (and ideally, be tracked)
			return m_Next.AsyncProcessMessage(im, ims);
		} 
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions