Click here to Skip to main content
15,886,075 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 542K   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.Concrete;
	using Kerosene.Tools;
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Linq.Expressions;
	using System.Transactions;

	// ==================================================== 
	public static partial class KMap
	{
		/// <summary>
		/// Reloads the given entity after the change operations have been executed.
		/// </summary>
		internal static void Reload(KMetaEntity meta)
		{
			object entity = meta.Entity; if (entity == null) return;
			if (meta.MetaMap == null) return;

			var cmd = meta.MetaMap.Query();
			cmd.Top(1);

			var tag = new DynamicNode.Argument("x");

			var id = new KRecordBase(meta.MetaMapInternal.IdSchema);
			meta.MetaMapInternal.WriteRecord(meta.Entity, id);

			for (int i = 0; i < id.Count; i++)
			{
				var left = new DynamicNode.GetMember(tag, id.Schema[i].ColumnName);
				var bin = new DynamicNode.Binary(left, ExpressionType.Equal, id[i]);
				cmd.Where(x => bin);
				bin.Dispose();
				left.Dispose();
			}
			tag.Dispose();
			id.Dispose();

			object obj = cmd.First(); cmd.Dispose(); cmd = null;

			if (obj != null && !object.ReferenceEquals(obj, entity))
			{
				var temp = KMetaEntity.Locate(obj);

				meta.Record = temp.Record.Clone();
				meta.MetaMapInternal.LoadEntity(meta.Record, entity);
				meta.MetaMapInternal.CompleteEntity(meta);
			}
		}

		/// <summary>
		/// Executes the given action per each extended member in a change operation context.
		/// </summary>
		/// <param name="meta">The hosting entity.</param>
		/// <param name="mode">The current change mode, to filter out the extended members whose mode does not match.</param>
		/// <param name="action">The action to execute with the value of the extended member, or with its members in case
		/// it is a child-alike object.</param>
		internal static void DependencyLoop(KMetaEntity meta, KMapExtendedMemberMode mode, Action<object> action)
		{
			var entity = meta.Entity; if (entity == null) return;
			var map = meta.MetaMap; if (map == null) return;

			foreach (var xmember in map.ExtendedMembers)
			{
				if (xmember.Mode != mode) continue;
				var value = xmember.ElementInfo.GetValue(entity); if (value == null) continue;

				var type = xmember.ElementInfo.ElementType;
				var isList = KMap.IsTypeIList(type) || KMap.IsTypeIListGenericT(type);

				if (!isList) action(value);
				else
				{
					var list = (IEnumerable)value;
					var values = new List<object>(); foreach (var item in list) if (item != null) values.Add(item);
					foreach (var item in values) action(item);
					values.Clear(); values = null;
				}
			}
		}

		/// <summary>
		/// Marks to refresh or submit to update the dependent (parent or child) meta from the host meta given, in
		/// the context of an insert or update operation of the host meta, where the dependency meta was found in a
		/// ready state.
		/// </summary>
		internal static void MarkToRefreshOrUpdate(KMetaEntity dependentMeta, KMetaEntity hostMeta)
		{
			// The easy case, when the dependent entity has changes per-se...
			if (HasRecordChanges(dependentMeta))
			{
				dependentMeta.MetaMap.Update(dependentMeta.Entity).Submit();
				return;
			}

			var link = dependentMeta.MetaLink.Link;

			// Finding whether any child of the dependent entity (except from the source host) has changes...
			DependencyLoop(dependentMeta, KMapExtendedMemberMode.Child, child =>
			{
				var childMeta = KMetaEntity.Locate(child, raise: false);
				if (childMeta == null) return;
				if (object.ReferenceEquals(childMeta, hostMeta)) return;

				var childType = child.GetType();
				IKMetaMap map = null;

				switch (childMeta.State)
				{
					case KMetaState.Detached:
						map = link.Map(childType);
						if (map == null) map = link.Map(childType.BaseType);
						if (map == null) throw new NotFoundException(string.Format("No map found for type '{0}' on link '{1}'.", childType.EasyName(), link));
						map.Insert(child).Submit();
						break;
					case KMetaState.Ready:
						if (HasRecordChanges(childMeta)) childMeta.MetaMap.Update(child).Submit(); // Limited to changes on the child it-self, no infinite cascade
						break;
				}
			});

			// In any case at least we want the dependent one to be marked to refresh...
			dependentMeta.ToRefresh = true;
		}
	}

	// ==================================================== Unit of Work pattern support
	public partial class KMetaLink
	{
		/// <summary>
		/// Gets and sets whether to use transactions when submitting changes or not.
		/// <para>Its default value is yes.</para>
		/// <para>The setter is provided to support debug and other specialized scenarios.</para>
		/// </summary>
		public bool UseTransactionsWhenSubmitChanges
		{
			get { return _UseTransactionsWhenSubmitChanges; }
			set { _UseTransactionsWhenSubmitChanges = value; }
		}

		/// <summary>
		/// Submits to the database all the pending changes annotated on the entities governed by this meta link.
		/// </summary>
		public void SubmitChanges()
		{
			SubmitChanges(UseTransactionsWhenSubmitChanges);
		}

		/// <summary>
		/// Submits to the database all the pending changes annotated on the entities governed by this meta link,
		/// optionally using a transaction.
		/// </summary>
		/// <param name="withTransaction">Whether to use a transaction to cover all the changes submitted to the
		/// database by this operation or not.</param>
		public void SubmitChanges(bool withTransaction)
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			bool underTx = false;
			List<KMetaEntity> list = null;
			List<KMetaEntity> pending = null;
			try
			{
				DebugHelper.IndentWriteLine("\n- Executing changes for '{0}'...", this);

				if (withTransaction) Link.Transaction.Start();
				if (Transaction.Current != null || Link.Transaction.IsActive) underTx = true;

				WithMetaEntitiesLock(() =>
				{
					list = MetaEntities.FindAll(x =>
						x.Entity != null && x.MetaMap != null &&
						x.MetaOperation != null && !x.MetaOperation.Executed);

					foreach (var meta in list)
					{
						try
						{
							DebugHelper.IndentWriteLine("\n- Executing '{0}'...", meta);
							if(!meta.MetaOperation.Executed) meta.MetaOperationInternal.OnExecute();
						}
						catch (Exception e) { throw e; }
						finally { DebugHelper.Unindent(); }
					}
					if (withTransaction) Link.Transaction.Commit();

					DebugHelper.WriteLine("\n- Finalizing changes for '{0}'...", this);
					foreach (var meta in list) meta.MetaOperationInternal.OnFinalize();

					DebugHelper.WriteLine("\n- Reloading pendings for '{0}'...", this);
					pending = MetaEntities.FindAll(x =>
						x.Entity != null && x.MetaMap != null &&
						x.ToRefresh);

					foreach (var meta in pending) { meta.Completed = false; KMap.ResetProxyObtainedFlags(meta); }
					foreach (var meta in pending) KMap.Reload(meta);
				});
			}
			catch (Exception e)
			{
				DebugHelper.WriteLine("\n- Error: {0}.\n", e.ExtendedString());

				if (withTransaction) Link.Transaction.Abort();
				if (list != null)
				{
					DebugHelper.IndentWriteLine("\n- Reverting changes for '{0}'...", this);

					list.Reverse(); foreach (var meta in list)
					{
						try
						{
							DebugHelper.IndentWriteLine("\n- Reverting '{0}'...", meta);
							meta.MetaOperationInternal.OnRevert(underTx);
						}
						catch (Exception r)
						{
							DebugHelper.IndentWriteLine("\n- Error reverting '{0}'...", r.ExtendedString());
							DebugHelper.Unindent();
						}
						finally { DebugHelper.Unindent(); }
					}

					DebugHelper.Unindent();
				}

				throw e;
			}
			finally
			{
				if (pending != null) pending.Clear(); pending = null;
				if (list != null) list.Clear(); list = null;
				DiscardChangesLoop();

				DebugHelper.Unindent();
			}
		}

		/// <summary>
		/// Discards all the pending changes annotated on the entities governed by this meta link.
		/// </summary>
		public void DiscardChanges()
		{
			if (IsDisposed) throw new ObjectDisposedException(this.ToString());

			DebugHelper.IndentWriteLine("\n- Discarding changes for '{0}'...", this);
			DiscardChangesLoop();
			DebugHelper.Unindent();
		}

		void DiscardChangesLoop()
		{
			WithMetaEntitiesLock(() =>
			{
				var list = MetaEntities.FindAll(x => x.MetaOperation != null || x.ToRefresh);
				foreach (var meta in list)
				{
					if (meta.MetaOperation != null) meta.MetaOperation.Dispose(); meta.MetaOperationInternal = null;
					meta.ToRefresh = 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