Click here to Skip to main content
15,889,808 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 544.1K   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.
// ======================================================== #undef DEBUG
namespace Kerosene.ORM.Maps
{
	using Kerosene.ORM.Core;
	using Kerosene.Tools;
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Text;
	using System.Threading;

	// ==================================================== 
	public static partial class KMap
	{
		internal static ulong _LastSerialMetaLink = 0;
		internal const string _MetaLinkTag = "::KMetaLink::";
	}

	// ==================================================== Structure and Configuration
	/// <summary>
	/// Represents extensions of a generic connection (or data context) in order to support maps and entities.
	/// </summary>
	public partial class KMetaLink : IKMetaLink, IDisposableExtended, ICloneableWithLink
	{
		/// <summary>
		/// Locates the meta link associated with the given link, creating it if needed and requested.
		/// </summary>
		internal static KMetaLink Locate(IKLink link, bool create = true)
		{
			if (link == null) throw new ArgumentNullException("link", "Link cannot be null.");
			if (link.IsDisposed)
			{
				if (create) throw new ObjectDisposedException(link.ToString());
				return null;
			}

			KMetaLink metalink = null; WithExtendedInfoLock(link, () =>
			{
				foreach (var kvp in link.ExtendedInfo)
					if (kvp.Key == KMap._MetaLinkTag) { metalink = (KMetaLink)kvp.Value; break; }

				if (metalink == null && create)
				{
					metalink = new KMetaLink(); link.ExtendedInfo.Add(KMap._MetaLinkTag, metalink);
					metalink._Link = link;
					metalink._SerialId = ++KMap._LastSerialMetaLink;
					metalink.EnableCollector();
				}
			});
			return metalink;
		}

		/// <summary>
		/// Executes the given action under a lock on the extended info repository of the given link.
		/// </summary>
		internal static void WithExtendedInfoLock(IKLink link, Action action)
		{
			lock (((ICollection)link.ExtendedInfo).SyncRoot) { action(); }
		}

		/// <summary>
		/// Gets whether the extended info repository of the given link is currently under a lock or not.
		/// </summary>
		internal static bool IsExtendedInfoLocked(IKLink link)
		{
			return Monitor.IsEntered(((ICollection)link.ExtendedInfo).SyncRoot);
		}

		bool _IsDisposed = false;
		IKLink _Link = null;
		ulong _SerialId = 0;

		System.Timers.Timer _Timer = null; // Null to avoid auto start
		int _Interval = KMap.DefaultCollectorInterval; // Default value unless modified by the app
		bool _InvokeGC = KMap.DefaultCollectorGCEnabled; // Default value unless modified by the app

		List<IKMetaMapInternal> _MetaMaps = new List<IKMetaMapInternal>();
		List<KMetaEntity> _MetaEntities = new List<KMetaEntity>();
		bool _UseTransactionsWhenSubmitChanges = KMap.DefaultUseTransactionsWhenSubmitChanges;

		private KMetaLink() { }

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

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

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

		protected virtual void OnDispose(bool disposing)
		{
			if (!IsDisposed && disposing)
			{
				if (_Timer != null)
				{
					_Timer.Stop(); _Timer.Dispose();
					_Timer = null;
				}

				if (_MetaMaps != null) WithMetaMapsLock(() =>
				{
					var list = new List<IKMetaMapInternal>(_MetaMaps); foreach (var map in list) map.Dispose();
					list.Clear(); list = null;
					_MetaMaps.Clear();
				});

				if (_MetaEntities != null) WithMetaEntitiesLock(() =>
				{
					var list = new List<KMetaEntity>(_MetaEntities); foreach (var meta in list) meta.Clear();
					list.Clear(); list = null;
					_MetaEntities.Clear();
				});

				if (_Link != null && !_Link.IsDisposed)
				{
					WithExtendedInfoLock(_Link, () => { _Link.ExtendedInfo.Remove(KMap._MetaLinkTag); });
					_Link = null;
				}
			}
			_IsDisposed = true;
		}

		/// <summary>
		/// Returns a new instance that is a copy of this original one, but associated with the new link reference
		/// given.
		/// </summary>
		/// <param name="newlink">The link reference the new cloned instance will be associated with.</param>
		internal KMetaLink Clone(IKLink newlink)
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			KMetaLink cloned = null; WithExtendedInfoLock(newlink, () =>
			{
				cloned = KMetaLink.Locate(newlink, create: false);
				if (cloned != null) throw new NotEmptyException(string.Format(
					"Link '{0}' already has a meta link associated with it.", newlink));

				cloned = KMetaLink.Locate(newlink, create: true);
				if (cloned == null) throw new CannotCreateException(string.Format(
					"Cannot create a meta link for link '{0}'.", newlink));

				WithMetaMapsLock(() => { foreach (var map in _MetaMaps) map.Clone(newlink); });
			});
			return cloned;
		}
		object ICloneableWithLink.Clone(IKLink newlink)
		{
			return this.Clone(newlink);
		}

		/// <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})",
				SerialId,
				GetType().EasyName(),
				Link == null ? "-" : Link.ToString());

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

		/// <summary>
		/// Gets the serial id assigned to this instance.
		/// </summary>
		public ulong SerialId
		{
			get { return _SerialId; }
		}

		/// <summary>
		/// The link this meta link is associated with.
		/// </summary>
		public IKLink Link
		{
			get { return _Link; }
		}

		/// <summary>
		/// Gets the collection of maps registered into this data context.
		/// </summary>
		internal List<IKMetaMapInternal> MetaMaps
		{
			get { return _MetaMaps; }
		}

		/// <summary>
		/// Gets whether the store of registered maps is locked or not.
		/// </summary>
		internal bool IsMetaMapsLocked
		{
			get { return Monitor.IsEntered(((ICollection)_MetaMaps).SyncRoot); }
		}

		/// <summary>
		/// Executes the given action with a lock on the store of registered maps.
		/// </summary>
		/// <param name="action">The action to execute.</param>
		internal void WithMetaMapsLock(Action action)
		{
			var enabled = _Timer == null ? false : _Timer.Enabled; if (enabled) _Timer.Enabled = false;
			lock (((ICollection)_MetaMaps).SyncRoot) { action(); }
			if (enabled) _Timer.Enabled = true;
		}

		/// <summary>
		/// Returns the map that has been registered into this instance for the given type, or null if no such map
		/// can be found.
		/// </summary>
		/// <param name="type">The type of the entities managed by the map.</param>
		public IKMetaMap Map(Type type)
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());
			if (type == null) throw new ArgumentNullException("type", "Type cannot be null.");

			IKMetaMap map = null; WithMetaMapsLock(() => { map = _MetaMaps.Find(x => x.EntityType == type); });
			return map;
		}

		/// <summary>
		/// Returns the map that has been registered into this instance for the given type, or null if no such map
		/// can be found.
		/// </summary>
		/// <typeparam name="T">The type of the entities managed by the map.</typeparam>
		public KMetaMap<T> Map<T>() where T : class
		{
			return (KMetaMap<T>)Map(typeof(T));
		}

		/// <summary>
		/// Gets the collection of maps that are currently registered into this link.
		/// </summary>
		public IEnumerable<IKMetaMap> Maps()
		{
			return _MetaMaps;
		}

		/// <summary>
		/// Removes and disposes all the maps that are registered into this instance.
		/// </summary>
		public void ClearMaps()
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			WithMetaMapsLock(() =>
			{
				var list = new List<IKMetaMap>(_MetaMaps); foreach (var map in list) map.Dispose();
				list.Clear(); list = null;
				_MetaMaps.Clear();
			});
		}

		/// <summary>
		/// Gets the collection of entities currently managed by this data context.
		/// </summary>
		internal List<KMetaEntity> MetaEntities
		{
			get { return _MetaEntities; }
		}

		/// <summary>
		/// Gets whether the cache of entities is currently under a lock or not.
		/// </summary>
		internal bool IsMetaEntitiesLocked
		{
			get { return Monitor.IsEntered(((ICollection)_MetaEntities).SyncRoot); }
		}

		/// <summary>
		/// Executes the given action with a lock on the cache of entities.
		/// </summary>
		/// <param name="action">The action to execute.</param>
		internal void WithMetaEntitiesLock(Action action)
		{
			var enabled = _Timer == null ? false : _Timer.Enabled; if (enabled) _Timer.Enabled = false;
			lock (((ICollection)_MetaEntities).SyncRoot) { action(); }
			if (enabled) _Timer.Enabled = true;
		}

		/// <summary>
		/// Removes all the entities that may have been kept in this instance's cache.
		/// </summary>
		public void ClearCache()
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			WithMetaEntitiesLock(() =>
			{
				var list = new List<KMetaEntity>(_MetaEntities); foreach (var meta in list) meta.Clear();
				list.Clear(); list = null;
				_MetaEntities.Clear();
			});
		}
	}

	// ==================================================== Collector API
	public partial class KMetaLink
	{
		/// <summary>
		/// Enables the collector of this link or resumes it.
		/// <para>This method is provided for debug or specialized scenarios only.</para>
		/// </summary>
		public void EnableCollector()
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			if (_Timer == null)
			{
				_Timer = new System.Timers.Timer();
				_Timer.Elapsed += new System.Timers.ElapsedEventHandler(Collector);
			}
			_Timer.Interval = _Interval;
			_Timer.Enabled = true;
		}

		/// <summary>
		/// Enables the collector for this lilnk or resumes it, modifying its operational parameters.
		/// <para>This method is provided for debug or specialized scenarios only.</para>
		/// </summary>
		/// <param name="milliseconds">The interval in milliseconds at which the collector is fired. If -1 then the
		/// default value is used.</param>
		/// <param name="invokeGC">Whether to force a CLR garbage collection before the collector is fired.</param>
		public void EnableCollector(int milliseconds, bool invokeGC)
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			if (milliseconds < 0) milliseconds = KMap.DefaultCollectorInterval;
			if (milliseconds < KMap.DefaultCollectorMinimumInterval) milliseconds = KMap.DefaultCollectorMinimumInterval;

			_Interval = milliseconds;
			_InvokeGC = invokeGC;

			EnableCollector();
		}

		/// <summary>
		/// Disables the collector of this manager.
		/// <para>This method is provided for debug or specialized scenarios only.</para>
		/// </summary>
		public void DisableCollector()
		{
			if (_Timer != null) _Timer.Stop();
		}

		/// <summary>
		/// Gets whether the collector of this manager is enabled or not.
		/// <para>This provided is provided for debug or specialized scenarios only.</para>
		/// </summary>
		public bool CollectorEnabled
		{
			get { return _Timer == null ? false : _Timer.Enabled; }
		}
	}

	// ==================================================== Collector call-back and Loop
	public partial class KMetaLink
	{
		void Collector(object source, System.Timers.ElapsedEventArgs args)
		{
			if (!IsDisposed)
			{
				DebugHelper.Indent();
				if (!IsMetaEntitiesLocked)
				{
					DebugHelper.WriteLine("\n- Fired collector of '{0}'...", this);
					if (_InvokeGC) GC.Collect();
					MetaEntitiesLoop(null);
				}
				else { DebugHelper.WriteLine("\n- Skipped collector of '{0}'...", this); }
				DebugHelper.Unindent();
			}
		}

		/// <summary>
		/// Iterates through the collection of meta entities, releasing them if neccesary, and otherwise executing
		/// the given function with each of them, if that function is not null. If the function returns true the loop
		/// is aborted. If returns false the loop continues.
		/// </summary>
		internal void MetaEntitiesLoop(Func<KMetaEntity, bool> func = null)
		{
			WithMetaEntitiesLock(() =>
			{
				for (int i = 0, count = _MetaEntities.Count; i < count; i++)
				{
					var meta = _MetaEntities[i];
					if (meta.State == KMetaState.Collected || meta.State == KMetaState.Detached)
					{
						DebugHelper.IndentWriteLine("\n- Collecting '{0}'...", meta);
						_MetaEntities.RemoveAt(i); i--; count--;
						meta.Clear();
						DebugHelper.Unindent();
					}
					else if (func != null && func(meta)) break;
				}
			});
		}
	}
}
// ======================================================== 

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