Click here to Skip to main content
15,891,567 members
Articles / Hosted Services / Azure

Kerosene ORM: a dynamic, configuration-less and self-adaptive ORM for POCO objects supporting a SQL-like syntax from C#

Rate me:
Please Sign up or sign in to vote.
4.96/5 (71 votes)
1 Mar 2015CPOL35 min read 545.6K   4.6K   212  
The seventh version of the dynamic, configuration-less and self-adaptive Kerosene ORM library, that provides full real support for POCO objects, natural SQL-like syntax from C#, and advanced capabilities while being extremely easy to use.
// ======================================================== 
namespace Kerosene.ORM.WCF.Server
{
	using Kerosene.ORM.Core;
	using Kerosene.Tools;
	using System;
	using System.Collections.Generic;
	using System.ServiceModel;
	using System.Text;

	// ==================================================== 
	/// <summary>
	/// Represents the server side of a WCF connection, or data context, with an arbitrary underlying database
	/// managed by this proxy.
	/// </summary>
	[ServiceBehavior(
		InstanceContextMode = InstanceContextMode.PerSession, // Each client has its own session (proxy object)
		IncludeExceptionDetailInFaults = true // To send back the exceptions to the client
		)]
	public abstract class KServerWCF : IKProxyWCF
	{
		bool _IsDisposed = false;
		Guid _ProxyId = Guid.NewGuid();
		IKLink _Link = null;
		DeepObject _Package = null;
		Dictionary<Guid, object> _Elements = new Dictionary<Guid, object>();

		/// <summary>
		/// Creates a new un-initialized proxy server.
		/// </summary>
		protected KServerWCF() { }

		/// <summary>
		/// Call-back method to create or obtain a link to serve the requests of the client of this server side
		/// proxy instance.
		/// </summary>
		protected abstract IKLink CreateLink();

		/// <summary>
		/// Gets whether this instance has been disposed or not.
		/// </summary>
		public bool IsDisposed
		{
			get { return _IsDisposed; }
		}

		/// <summary>
		/// Disposes this instance.
		/// </summary>
		public void Dispose()
		{
			if (!IsDisposed) { OnDispose(true); GC.SuppressFinalize(this); }
		}

		~KServerWCF()
		{
			if (!IsDisposed) OnDispose(false);
		}

		protected virtual void OnDispose(bool disposing)
		{
			if (!IsDisposed && disposing)
			{
				if (_Link != null) OnProxyDisconnect();
				_Link = null;
			}
			_IsDisposed = true;
		}

		/// <summary>
		/// Returns the string representation of this instance.
		/// </summary>
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			if (IsDisposed) sb.Append("disposed::[");

			sb.AppendFormat("{0}({1}: {2})",
				GetType().EasyName(),
				ProxyId.TagString(),
				Link == null ? "-" : Link.ToString());

			if (IsDisposed) sb.Append("]");
			return sb.ToString();
		}

		/// <summary>
		/// Gets the uid assigned to this instance.
		/// </summary>
		public Guid ProxyId
		{
			get { return _ProxyId; }
		}

		/// <summary>
		/// Gets the link that represents the underlying database this server side proxy connects to on behalf of
		/// the client it is connected with.
		/// </summary>
		public IKLink Link
		{
			get { return _Link; }
		}

		/// <summary>
		/// Gets the package or arbitrary information the client used to connect to this server side proxy, or
		/// null if no one was used, if this instance is not connected, or if it was disposed.
		/// </summary>
		public DeepObject Package
		{
			get { return _Package; }
		}

		/// <summary>
		/// Invoked when connecting with a client.
		/// </summary>
		/// <param name="package">The optional package of arbitrary information sent by the client when it sent
		/// the connection request, that this server side proxy can use to tailor the details of the services it
		/// will provide to that client.</param>
		public virtual Guid OnProxyConnect(DeepObject package = null)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				package == null ? "" : package.ToString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is already connected.", this));

				_Package = package;
				_Link = CreateLink(); if (_Link == null) throw new CannotCreateException(string.Format(
					"Cannot create a new link for this '{0}'.", this));

				return _ProxyId;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked when the client wants to disconnect from this server side proxy.
		/// </summary>
		public virtual void OnProxyDisconnect()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());

				foreach (var kvp in _Elements)
				{
					if (kvp.Value == null) continue;

					if (kvp.Value is IDisposableExtended) { if (!((IDisposableExtended)kvp.Value).IsDisposed) ((IDisposable)kvp.Value).Dispose(); }
					else if (kvp.Value is IDisposable) ((IDisposable)kvp.Value).Dispose();
				}
				_Elements.Clear();

				if (_Link != null)
				{
					if (_Link.IsOpen) _Link.Close();
					_Link.Dispose();
					_Link = null;
				}

				_Package = null;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Gets whether this server side proxy is currently connected or not.
		/// </summary>
		/// <returns></returns>
		public bool IsConnected()
		{
			return !(_Link == null);
		}

		/// <summary>
		/// Invoked to open the actual server side connection with the arbitrary underlying database this server
		/// creates to serve the requests of its client
		/// <para>If the connection was already opened this method throws an exception.</para>
		/// </summary>
		public virtual void OnLinkOpen()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				_Link.Open();
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to close the server side connection with the arbitrary underlying database this server created
		/// to serve the requests of its client.
		/// <para>If the connection was not opened this method is merely ignored.</para>
		/// </summary>
		public virtual void OnLinkClose()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());

				if (_Link != null) _Link.Close();
				_Link = null;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to get whether the server side connection with the arbitrary underlying database created by
		/// this server is opened or not.
		/// </summary>
		public bool OnLinkIsOpen()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				return _Link.IsOpen;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to start a new transaction at the server side or, if it was active already, to increase its
		/// nesting level.
		/// </summary>
		public virtual void OnTransactionStart()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				_Link.Transaction.Start();
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to commit the transaction at the server side or, if its nesting level is greter than one, to
		/// decrease that nesting level.
		/// <para>This method is merely ignored is the transaction was not active.</para>
		/// </summary>
		public virtual void OnTransactionCommit()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				_Link.Transaction.Commit();
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to abort the transaction at the server side regardless of its nesting level.
		/// <para>This method is merely ignored is the transaction was not active.</para>
		/// </summary>
		public virtual void OnTransactionAbort()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				_Link.Transaction.Abort();
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to obtain the level of the transaction at the server side. A value of cero indicates that the
		/// server side transaction is not active.
		/// </summary>
		public int OnTransactionLevel()
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}()", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false));
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				return _Link.Transaction.Level;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to create a new server side enumerator.
		/// </summary>
		/// <param name="text">The text of the command to be executed by the new enumerator.</param>
		/// <param name="pars">the list of parameters to be used to execute the command of the new enumerator.</param>
		public virtual Guid OnEnumeratorCreate(string text, IKParameterCollection pars)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3}, {4})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				text ?? "-",
				pars == null ? "-" : pars.ToString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				var cmd = Link.Raw();
				cmd.Set(text);
				if(pars != null) foreach (var par in pars) cmd.Parameters.Add(par.Clone());

				var obj = cmd.GetEnumerator();
				var uid = Guid.NewGuid();

				_Elements.Add(uid, obj);
				return uid;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to dispose the server side enumerator.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the enumerator.</param>
		public virtual void OnEnumeratorDispose(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));

				var obj = (IKEnumerator)temp;
				var cmd = (IKCommandRaw)obj.Command;

				obj.Dispose();
				cmd.Dispose();
				_Elements.Remove(uid);
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to execute the command associated with the server side enumerator. Returns the schema that
		/// describes the structure and metadata of the objects to return.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the enumerator.</param>
		public virtual IKSchema OnEnumeratorStart(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));

				var obj = (IKEnumerator)temp;
				var schema = obj.OnReaderStart();

				// MessageRpc disposes its 'ReturnParameter' if its implements 'IDisposable', which would cause
				// the schema to be disposed. For this reason we return a clone, that at the same time will also
				// be cloned at the client side to use there that local copy.

				if (schema != null) schema = schema.Clone();
				return schema;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to obtain the next record of the iteration, or null if no more records are available.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the enumerator.</param>
		public virtual IKRecord OnEnumeratorNext(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));

				var obj = (IKEnumerator)temp;
				var record = obj.OnReaderNext();

				// MessageRpc disposes its 'ReturnParameter' if its implements 'IDisposable', which would cause
				// the record to be disposed. For this reason we return a clone, that at the same time will also
				// be cloned at the client side to use there that local copy.

				if (record != null) record = record.Clone();
				return record;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to reset the server side enumerator.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the enumerator.</param>
		public virtual void OnEnumeratorReset(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Enumerator '{0}' not found.", uid.TagString()));

				var obj = (IKEnumerator)temp;
				obj.Reset();
#if DEBUG
				// We force a collection at the server, in debug mode, to verify that collecting the objects created
				// here at the server side do not interfere with the client side copies...

				DebugHelper.IndentWriteLine("\n- GC Collector invoked ... {0}", _Elements.Count);
				GC.Collect();
				DebugHelper.Unindent();
#endif
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to create a new server side exectutor.
		/// </summary>
		/// <param name="text">The text of the command to be executed by the new exectutor.</param>
		/// <param name="pars">the list of parameters to be used to execute the command of the new exectutor.</param>
		public virtual Guid OnExecutorCreate(string text, IKParameterCollection pars)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3}, {4})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				text ?? "-",
				pars == null ? "-" : pars.ToString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				var cmd = Link.Raw();
				cmd.Set(text);
				if (pars != null) foreach (var par in pars) cmd.Parameters.Add(par.Clone());

				var obj = cmd.GetExecutor();
				var uid = Guid.NewGuid();

				_Elements.Add(uid, obj);
				return uid;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to dispose the server side executor.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the executor.</param>
		public virtual void OnExecutorDispose(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Executor '{0}' not found.", uid.TagString()));

				var obj = (IKExecutor)temp;
				var cmd = (IKCommandRaw)obj.Command;

				obj.Dispose();
				cmd.Dispose();
				_Elements.Remove(uid);
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}

		/// <summary>
		/// Invoked to execute the server side executor, returning an integer that typically represents the number
		/// of records that were affected by the command, among other possible meanings.
		/// </summary>
		/// <param name="uid">The server side uid that identifies the executor.</param>
		public virtual int OnExecutorExecute(Guid uid)
		{
			DebugHelper.IndentWriteLine("\n- {0}[{1}].{2}({3})", GetType().EasyName(), _ProxyId.TagString(), MethodHelper.ThisMethod(false),
				uid.TagString());
			try
			{
				if (IsDisposed) throw new ObjectDisposedException(this.ToString());
				if (!IsConnected()) throw new InvalidOperationException(string.Format("This '{0}' is not connected.", this));

				object temp = null; if (!_Elements.TryGetValue(uid, out temp))
					throw new NotFoundException(string.Format("Executor '{0}' not found.", uid.TagString()));

				var obj = (IKExecutor)temp;

				var r = obj.Execute();
				return r;
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Exception[{0}]: {1}", _ProxyId.TagString(), e.ExtendedString());
				throw e;
			}
			finally { DebugHelper.Unindent(); }
		}
	}
}
// ======================================================== 

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Spain Spain
mbarbac has worked in start-ups, multinational tech companies, and consulting ones, serving as CIO, CTO, SW Development Director, and Consulting Director, among many other roles.

Solving complex puzzles and getting out of them business value has ever been among his main interests - and that's why he has spent his latest 25 years trying to combine his degree in Theoretical Physics with his MBA... and he is still trying to figure out how all these things can fit together.

Even if flying a lot across many countries, along with the long working days that are customary in IT management and Consultancy, he can say that, after all, he lives in Spain (at least the weekends).

Comments and Discussions