Click here to Skip to main content
Click here to Skip to main content
Articles » Database » Database » ADO.NET » Downloads
 
Add your own
alternative version

Declarative Transactions using ADO.NET and without Enterprise Services.

, 26 Oct 2001
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.
declarativetransactions_src.zip
codeproject_template
TransactionAttribute
bin
Debug
obj
Debug
temp
TempPE
TransactionAttribute.csproj.user
TransactionExample
bin
Debug
obj
Debug
temp
TempPE
TransactionExample.csproj.user
TransactionExample.suo
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Sandy Place

Canada Canada
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 27 Oct 2001
Article Copyright 2001 by Sandy Place
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid