Click here to Skip to main content
15,892,517 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 546.3K   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.
// ======================================================== SurrogateDirect.cs
namespace Kerosene.ORM.Direct.Concrete
{
	using Kerosene.Tools;
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Data;
	using System.Linq;
	using System.Runtime.Serialization;
	using System.Text;
	using System.Transactions;

	// ==================================================== 
	/// <summary>
	/// Acts as a surrogate for direct enumerator or direct executor operations.
	/// </summary>
	internal class SurrogateDirect : IDisposableEx
	{
		bool _IsDisposed = false;
		Core.ICommand _Command = null;

		bool _LinkOpenedBySurrogate = false;
		IDbCommand _DbCommand = null;
		IDataReader _DataReader = null;
		Core.ISchema _Schema = null;

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// <param name="command">The command this instance is associated with.</param>
		public SurrogateDirect(Core.ICommand command)
		{
			if ((_Command = command) == null) throw new ArgumentNullException(
				"command", "Command cannot be null.");

			if (_Command.Link == null) throw new InvalidOperationException(
				"Link of command '{0}' is null."
				.FormatWith(_Command));

			if (!(_Command.Link is Direct.IDataLink)) throw new InvalidOperationException(
				"Link '{0}' of command '{1}' is not a direct link."
				.FormatWith(_Command.Link, _Command));
		}

		/// <summary>
		/// 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); }
		}

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

		/// <summary>
		/// Invoked when disposing or finalizing this instance.
		/// </summary>
		/// <param name="disposing">True if the object is being disposed, false otherwise.</param>
		protected virtual void OnDispose(bool disposing)
		{
			if (_DbCommand != null)
			{
				_DbCommand.Cancel(); if (disposing) _DbCommand.Dispose();
				_DbCommand = null;
			}
			if (_DataReader != null)
			{
				if (!_DataReader.IsClosed) _DataReader.Close(); if (disposing) _DataReader.Dispose();
				_DataReader = null;
			}
			if (Link != null)
			{
				if (Link.IsOpen && _LinkOpenedBySurrogate) Link.Close();
				_LinkOpenedBySurrogate = false;
			}

			_Command = null;
			_Schema = null;

			_IsDisposed = true;
		}

		/// <summary>
		/// Returns the string representation of this instance.
		/// </summary>
		/// <returns>A string containing the standard representation of this instance.</returns>
		public override string ToString()
		{
			string str = string.Format("{0}({1}, {2})",
				GetType().EasyName(),
				Command == null ? string.Empty : Command.ToString(),
				Link == null ? string.Empty : Link.ToString());

			return IsDisposed ? string.Format("disposed::{0}", str) : str;
		}

		/// <summary>
		/// The command this surrogate refers to.
		/// </summary>
		internal Core.ICommand Command
		{
			get { return _Command; }
		}

		/// <summary>
		/// The direct data link the command of this surrogate refers to, or null.
		/// </summary>
		internal IDataLink Link
		{
			get { return (_Command == null ? null : _Command.Link) as IDataLink; }
		}

		/// <summary>
		/// Adds the command parameters to the database command.
		/// </summary>
		private void OnInvokeAddParameters()
		{
			if (_Command.Parameters.Count == 0) return;

			var nstr = Link.Parser.Parse(null, pc: null, nulls: true);
			var comp = Link.Engine.CaseSensitiveNames ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;

			// Positional parameters...
			if (Link.Engine.PositionalParameters)
			{
				var pars = new List<Core.IParameter>(); foreach (var par in _Command.Parameters)
				{
					if (par.Value == null) _DbCommand.CommandText = _DbCommand.CommandText.Replace(par.Name, nstr);
					else pars.Add(par);
				}

				var list = new List<Tuple<int, Core.IParameter>>(); foreach (var par in pars)
				{
					var i = _DbCommand.CommandText.IndexOf(par.Name, comp);
					list.Add(new Tuple<int, Core.IParameter>(i, par));
				}
				list.Sort((a, b) => a.Item1.CompareTo(b.Item1));

				foreach (var tuple in list)
				{
					var temp = _DbCommand.CreateParameter();
					temp.ParameterName = tuple.Item2.Name;
					temp.Value = Link.Engine.TryTransform(tuple.Item2.Value);
					_DbCommand.Parameters.Add(temp);
				}

				list.Clear(); list = null;
				pars.Clear(); pars = null;
			}

			// Non-positional parameters...
			else
			{
				foreach (var par in _Command.Parameters)
				{
					if (par.Value == null) _DbCommand.CommandText = _DbCommand.CommandText.Replace(par.Name, nstr);
					else
					{
						var temp = _DbCommand.CreateParameter();
						temp.ParameterName = par.Name;
						temp.Value = Link.Engine.TryTransform(par.Value);
						_DbCommand.Parameters.Add(temp);
					}
				}
			}
		}

		/// <summary>
		/// Enlist the invocation into the connection's transaction if needed.
		/// </summary>
		private void OnInvokeAddTransaction()
		{
			if (Transaction.Current != null) return; // Managed externally by the global scope...

			var flags = TypeEx.InstancePublicAndHidden;
			object inner = null;

			if (DynamicInfo.TryRead(Link.DbConnection, x => x.InnerConnection, out inner, flags) == null)
			{
				object current = null;
				if (DynamicInfo.TryRead(inner, x => x.CurrentTransaction, out current, flags) == null)
				{
					object parent = null;
					if (DynamicInfo.TryRead(current, x => x.Parent, out parent, flags) == null)
					{
						if (parent != null) _DbCommand.Transaction = (IDbTransaction)parent;
					}
				}
			}
		}

		/// <summary>
		/// Invokes the execution or enumeration of the command.
		/// </summary>
		private void Invoke(bool iterable, Action action)
		{
			if (Command.IsDisposed) throw new ObjectDisposedException(Command.ToString());
			if (Link.IsDisposed) throw new ObjectDisposedException(Link.ToString());

			if (!Command.CanBeExecuted) throw new CannotExecuteException(
				"Command '{0}' cannot be executed.".FormatWith(Command));

			try
			{
				if (!Link.IsOpen)
				{
					_LinkOpenedBySurrogate = true;
					Link.Open();
				}
				if (Link.DbConnection == null) throw new EmptyException(
					"Link '{0}' of command '{1}' cannot be connected.".FormatWith(Link, _Command));

				var cmd = _Command.GetCommandText(iterable);
				if (cmd == null) throw new InvalidOperationException(
					"Cannot generate the text of command '{0}'.".FormatWith(_Command));

				_DbCommand = Link.DbConnection.CreateCommand();
				_DbCommand.CommandText = cmd;

				OnInvokeAddParameters();
				OnInvokeAddTransaction();

				action();
			}
			catch
			{
				try { OnDispose(true); }
				catch { }

				throw;
			}
			finally
			{
				if (_DbCommand != null)
				{
					_DbCommand.Dispose();
					_DbCommand = null;
				}
			}
		}

		/// <summary>
		/// Invoked to execute the scalar command and to return the integer that execution
		/// produces.
		/// </summary>
		internal int OnExecuteScalar()
		{
			int r = 0; Invoke(iterable: false, action: () => { r = _DbCommand.ExecuteNonQuery(); });
			Dispose();
			return r;
		}

		/// <summary>
		/// Invokes to start the execution of the enumerable command and to return the schema
		/// that describes the structure of the records to be produced by that execution.
		/// </summary>
		internal Core.ISchema OnReaderStart()
		{
			_Schema = null; Invoke(iterable: true, action: () =>
			{
				_DataReader = _DbCommand.ExecuteReader(CommandBehavior.KeyInfo);

				var table = _DataReader.GetSchemaTable(); if (table == null)
				{
					var s = "Cannot obtain schema for command '{0}'.".FormatWith(_Command);
					if (!_DbCommand.CommandText.ToUpper().Contains("OUTPUT")) s += " Have you used an 'OUTPUT' clause?";
					Dispose();
					throw new InvalidOperationException(s);
				}

				var name = _Command is Core.ITableNameProvider
					? ((Core.ITableNameProvider)_Command).TableName
					: null;

				_Schema = new Core.Concrete.Schema(Link.Engine.CaseSensitiveNames);

				for (int i = 0; i < table.Rows.Count; i++)
				{
					DataRow row = table.Rows[i];
					string meta = null;
					object value = null;

					bool hidden = false; if (table.Columns.Contains("IsHidden"))
					{
						value = row[table.Columns["IsHidden"]];
						if (!(value is DBNull)) hidden = (bool)value;
					}
					if (hidden) continue;

					var entry = new Core.Concrete.SchemaEntry(); for (int j = 0; j < table.Columns.Count; j++)
					{
						meta = table.Columns[j].ColumnName;
						value = row[j] is DBNull ? null : row[j];
						entry[meta] = value;
					}

					if (entry.TableName == null && name != null) entry.TableName = name;

					_Schema.Add(entry);
				}
				table.Dispose(); table = null;

				if (_Schema.Count == 0) throw new InvalidOperationException(
					"Schema is empty after executing command '{0}'".FormatWith(_Command));

				if (_Command is Core.IElementAliasProvider)
					_Schema.Aliases.AddRange(
						((Core.IElementAliasProvider)_Command).Aliases,
						cloneNotOrphans: true);
			});
			return _Schema;
		}

		/// <summary>
		/// Invoked to retrieve the next available record produced by the execution of the
		/// enumerable command, or null if there are no more records available.
		/// </summary>
		internal Core.IRecord OnReaderNext()
		{
			Core.IRecord record = null;
			try
			{
				if (_DataReader.Read())
				{
					record = new Core.Concrete.Record(_Schema);

					for (int i = 0; i < _Schema.Count; i++)
						record[i] = _DataReader.IsDBNull(i) ? null : _DataReader.GetValue(i);

					return record;
				}
				else
				{
					OnDispose(true); return null;
				}
			}
			catch (Exception e)
			{
				try { OnDispose(true); }
				catch { }

				throw e;
			}
		}

		/// <summary>
		/// Invoked when reseting the enumeration of the command.
		/// </summary>
		internal void OnReaderReset()
		{
			if (_DbCommand != null)
			{
				_DbCommand.Cancel(); _DbCommand.Dispose();
				_DbCommand = null;
			}
			if (_DataReader != null)
			{
				if (!_DataReader.IsClosed) _DataReader.Close(); _DataReader.Dispose();
				_DataReader = null;
			}
			if (Link != null)
			{
				if (Link.IsOpen && _LinkOpenedBySurrogate) Link.Close();
				_LinkOpenedBySurrogate = false;
			}
		}
	}
}
// ======================================================== 

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