Click here to Skip to main content
15,896,111 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 548.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.
// ======================================================== ProxyGenerator.cs
namespace Kerosene.ORM.Maps.Concrete
{
	using Kerosene.ORM.Core;
	using Kerosene.ORM.Core.Concrete;
	using Kerosene.Tools;
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Linq;
	using System.Reflection;
	using System.Reflection.Emit;
	using System.Text;

	// ==================================================== 
	/// <summary>
	/// Represents the capability of generating extended types to manage the getters and setters
	/// of the virtual lazy properties of their original base types. This class is intended for
	/// internal usage only.
	/// </summary>
	public static partial class ProxyGenerator
	{
		const string PROXY_ASSEMBLY_NAME = "KeroseneRunTimeProxies";
		const int MAX_PROXY_NAME_LENGTH = 512 - 1;
		const string COMPLETEDFLAG_SUFFIX = "_Completed";
		const string SOURCEBACK_SUFFIX = "_Source";

		static List<ProxyHolder> _Holders = new List<ProxyHolder>();
		static AssemblyBuilder _AssemblyBuilder = null;
		static ModuleBuilder _ModuleBuilder = null;

		/// <summary>
		/// The list of proxy holders that have been generated.
		/// </summary>
		internal static List<ProxyHolder> Holders
		{
			get { return _Holders; }
		}

		/// <summary>
		/// Executes the given action under a lock on the holders' list.
		/// </summary>
		/// <param name="action">The action to execute.</param>
		internal static void WithHoldersLock(Action action)
		{
			lock (((ICollection)_Holders).SyncRoot) { action(); }
		}

		/// <summary>
		/// Initializes the proxy generator if needed.
		/// </summary>
		static void InitializeProxyGenerator()
		{
			if (_AssemblyBuilder == null)
			{
				AssemblyName asmName = new AssemblyName(PROXY_ASSEMBLY_NAME);

				_AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
					asmName,
					AssemblyBuilderAccess.Run);

				_ModuleBuilder = _AssemblyBuilder.DefineDynamicModule(
					_AssemblyBuilder.GetName().Name,
					emitSymbolInfo: false);
			}
		}

		/// <summary>
		/// Gets the list of virtual lazy properties associated to the type of the entities
		/// managed by the given map. The list is obtained from the collection of members that
		/// is currently defined for the map.
		/// </summary>
		static List<LazyProperty> GetLazyProperties<T>(DataMap<T> map) where T : class
		{
			var list = new List<LazyProperty>(); foreach (var member in map.Members)
			{
				if (!member.ElementInfo.IsProperty) continue; // Only properties can be virtual...
				if (member.ElementInfo.IsMultipart) continue; // Multipart lazy properties are not supported...
				if (member.CompleteMember == null) continue; // If no delegate there is no lazyness...

				var lazy = new LazyProperty();
				lazy.OriginalProperty = member.ElementInfo.PropertyInfo;
				lazy.OriginalGetter = lazy.OriginalProperty.GetGetMethod(nonPublic: true);
				lazy.OriginalSetter = lazy.OriginalProperty.GetSetMethod(nonPublic: true);

				bool valid = false;
				if (lazy.OriginalGetter != null && lazy.OriginalGetter.IsVirtual && !lazy.OriginalGetter.IsPrivate) valid = true;
				if (lazy.OriginalSetter != null && lazy.OriginalSetter.IsVirtual && !lazy.OriginalSetter.IsPrivate) valid = true;

				if (valid) list.Add(lazy);
			}

			list.Sort((a, b) => string.Compare(a.Name, b.Name));
			return list;
		}

		/// <summary>
		/// Gets the name of the proxy type associated with the given type and list of lazy
		/// properties, or null if no such proxy is needed.
		/// </summary>
		static string GetProxyTypeName<T>(List<LazyProperty> list) where T : class
		{
			if (list == null || list.Count == 0) return null;

			var type = typeof(T);
			var name = type.EasyName(depth: int.MaxValue);

			foreach (var entry in list) name = string.Format("{0}_{1}", name, entry.Name);
			if (name.Length > MAX_PROXY_NAME_LENGTH) throw new ArgumentOutOfRangeException(
				"Lenght of proxy name '{0}' is too big.".FormatWith(name));

			name = name.Replace(".", "$");
			return name;
		}

		/// <summary>
		/// Returns a holder valid for the given map, or null if not such holder is needed based
		/// upon the defined members of the map and its virtual lazy properties.
		/// </summary>
		/// <typeparam name="T">The type of the entities managed by the map.</typeparam>
		/// <param name="map">The map whose holder is to be obtained.</param>
		/// <returns>The requested holder or null.</returns>
		internal static ProxyHolder Locate<T>(DataMap<T> map) where T : class
		{
			InitializeProxyGenerator();
			var list = GetLazyProperties<T>(map);
			var name = GetProxyTypeName<T>(list); if (name == null) return null;

			ProxyHolder holder = null; lock (((ICollection)Holders).SyncRoot)
			{
				// If the appropriate holder exists, just return it...
				holder = Holders.Find(x => x.ExtendedType.Name == name);
				if (holder != null) { list.Clear(); return holder; }

				// Otherwise, create a new one...
				holder = new ProxyHolder();
				holder.LazyProperties.AddRange(list);
				foreach (var lazy in holder.LazyProperties) lazy.Holder = holder;

				// Preparing the builders...
				Type baseType = typeof(T);
				ILGenerator il = null;

				var proxyBuilder = _ModuleBuilder.DefineType(name,
					TypeAttributes.Public | TypeAttributes.AutoLayout |
					TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass |
					TypeAttributes.BeforeFieldInit,
					baseType, null);

				// The fields that maintain whether the extended property is loaded or not...
				foreach (var lazy in holder.LazyProperties)
					lazy.LazyCompletedFlag = proxyBuilder.DefineField(lazy.Name + COMPLETEDFLAG_SUFFIX,
						typeof(bool),
						FieldAttributes.Public);

				// Replicating the constructors on the extended type...
				var baseCons = baseType.GetConstructors(TypeEx.InstancePublicAndHidden);
				foreach (var baseCon in baseCons)
				{
					var pars = baseCon.GetParameters();
					var types = pars.Select(x => x.ParameterType).ToArray();

					var proxyCon = proxyBuilder.DefineConstructor(
						baseCon.Attributes,
						baseCon.CallingConvention,
						types);

					il = proxyCon.GetILGenerator(); foreach (var element in holder.LazyProperties)
					{
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Ldc_I4_0);
						il.Emit(OpCodes.Stfld, element.LazyCompletedFlag);
					}
					il.Emit(OpCodes.Ldarg_0);
					for (int i = 0; i < types.Length; i++) il.Emit(OpCodes.Ldarg, i + 1);
					il.Emit(OpCodes.Call, baseCon);
					il.Emit(OpCodes.Ret);
				}

				// Replicating the virtual properties on the extended type...
				var onProxySetter = typeof(ProxyGenerator).GetMethod("OnProxySetter", BindingFlags.Public | BindingFlags.Static);
				var onProxyGetter = typeof(ProxyGenerator).GetMethod("OnProxyGetter", BindingFlags.Public | BindingFlags.Static);

				// Treating the lazy properties...
				foreach (var lazy in holder.LazyProperties)
				{
					lazy.ExtendedProperty = proxyBuilder.DefineProperty(lazy.Name,
						lazy.OriginalProperty.Attributes,
						lazy.OriginalProperty.PropertyType, null);

					lazy.SourceBackProperty = proxyBuilder.DefineProperty(lazy.Name + SOURCEBACK_SUFFIX,
						lazy.OriginalProperty.Attributes,
						lazy.OriginalProperty.PropertyType, null);

					// The getter...
					if (lazy.OriginalGetter != null && lazy.OriginalGetter.IsVirtual && !lazy.OriginalGetter.IsPrivate)
					{
						var sourceBackMethod = proxyBuilder.DefineMethod("get_" + lazy.SourceBackProperty.Name,
							MethodAttributes.Public |
							MethodAttributes.SpecialName |
							MethodAttributes.HideBySig | MethodAttributes.Virtual,
							lazy.SourceBackProperty.PropertyType, null);

						il = sourceBackMethod.GetILGenerator();
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Call, lazy.OriginalGetter);
						il.Emit(OpCodes.Ret);

						((PropertyBuilder)lazy.SourceBackProperty).SetGetMethod(sourceBackMethod);

						var extendedMethod = proxyBuilder.DefineMethod("get_" + lazy.ExtendedProperty.Name,
							MethodAttributes.Public |
							MethodAttributes.SpecialName |
							MethodAttributes.HideBySig | MethodAttributes.Virtual,
							lazy.ExtendedProperty.PropertyType, null);

						il = extendedMethod.GetILGenerator();
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Ldstr, lazy.ExtendedProperty.Name);
						il.Emit(OpCodes.Call, onProxyGetter);
						il.Emit(OpCodes.Ret);

						((PropertyBuilder)lazy.ExtendedProperty).SetGetMethod(extendedMethod);
					}

					// The setter...
					if (lazy.OriginalSetter != null && lazy.OriginalSetter.IsVirtual && !lazy.OriginalSetter.IsPrivate)
					{
						var sourceBackMethod = proxyBuilder.DefineMethod("set_" + lazy.SourceBackProperty.Name,
							MethodAttributes.Public |
							MethodAttributes.SpecialName |
							MethodAttributes.HideBySig | MethodAttributes.Virtual,
							null, new[] { lazy.SourceBackProperty.PropertyType });

						il = sourceBackMethod.GetILGenerator();
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Ldarg_1);
						il.Emit(OpCodes.Call, lazy.OriginalSetter);
						il.Emit(OpCodes.Ret);

						((PropertyBuilder)lazy.SourceBackProperty).SetSetMethod(sourceBackMethod);

						var extendedMethod = proxyBuilder.DefineMethod("Set_" + lazy.ExtendedProperty.Name,
							MethodAttributes.Public |
							MethodAttributes.SpecialName |
							MethodAttributes.HideBySig | MethodAttributes.Virtual,
							null, new[] { lazy.ExtendedProperty.PropertyType });

						il = extendedMethod.GetILGenerator();
						il.Emit(OpCodes.Ldarg_0);
						il.Emit(OpCodes.Ldarg_1);
						il.Emit(OpCodes.Ldstr, lazy.ExtendedProperty.Name);
						il.Emit(OpCodes.Call, onProxySetter);
						il.Emit(OpCodes.Ret);

						((PropertyBuilder)lazy.ExtendedProperty).SetSetMethod(extendedMethod);
					}
				}

				// Generating the type...
				holder.ExtendedType = proxyBuilder.CreateType();
				Holders.Add(holder);

				// Before releasing the lock let's cache some relevant information...
				var type = holder.ExtendedType; foreach (var lazy in holder.LazyProperties)
				{
					lazy.LazyCompletedFlag = type.GetField(lazy.LazyCompletedFlag.Name, TypeEx.InstancePublicAndHidden);

					lazy.ExtendedProperty = type.GetProperty(lazy.ExtendedProperty.Name, TypeEx.InstancePublicAndHidden);
					lazy.ExtendedGetter = lazy.ExtendedProperty == null ? null : lazy.ExtendedProperty.GetGetMethod(nonPublic: true);
					lazy.ExtendedSetter = lazy.ExtendedProperty == null ? null : lazy.ExtendedProperty.GetSetMethod(nonPublic: true);

					lazy.SourceBackProperty = type.GetProperty(lazy.SourceBackProperty.Name, TypeEx.InstancePublicAndHidden);
					lazy.SourceBackGetter = lazy.SourceBackProperty == null ? null : lazy.SourceBackProperty.GetGetMethod(nonPublic: true);
					lazy.SourceBackSetter = lazy.SourceBackProperty == null ? null : lazy.SourceBackProperty.GetSetMethod(nonPublic: true);
				}
			}

			return holder;
		}
	}

	// ==================================================== 
	public static partial class ProxyGenerator
	{
		/// <summary>
		/// Call-back method used to set the value of a virtual lazy property.
		/// <para>FOR INTERNAL USAGE ONLY.</para>
		/// </summary>
		/// <remarks>Needs to be a public method in a public class.</remarks>
		public static void OnProxySetter(object entity, object value, string name)
		{
			var type = entity.GetType();
			var holder = Holders.Find(x => x.ExtendedType == type);
			var lazy = holder.LazyProperties.Find(x => x.Name == name);

			lazy.LazyCompletedFlag.SetValue(entity, true); // Avoiding re-entrance...
			lazy.SourceBackSetter.Invoke(entity, new[] { value });
		}

		/// <summary>
		/// Call-back method used to get the value of a virtual lazy property.
		/// <para>FOR INTERNAL USAGE ONLY.</para>
		/// </summary>
		/// <remarks>Needs to be a public method in a public class.</remarks>
		public static object OnProxyGetter(object entity, string name)
		{
			var type = entity.GetType();
			var holder = Holders.Find(x => x.ExtendedType == type);
			var lazy = holder.LazyProperties.Find(x => x.Name == name);

			var value = lazy.SourceBackGetter.Invoke(entity, null);

			var completed = (bool)lazy.LazyCompletedFlag.GetValue(entity);
			while (!completed)
			{
				var meta = MetaEntity.Locate(entity, create: false);
				if (meta == null) break;
				if (meta.Record == null) break;
				if (meta.Map == null) break;

				var member = meta.Map.Members.FirstOrDefault<IUberMember>(x => x.Name == name);
				if (member == null) break;
				if (member.CompleteMember == null) break;

				lazy.LazyCompletedFlag.SetValue(entity, true);
				member.CompleteMember(meta.Record, entity);
				value = lazy.SourceBackGetter.Invoke(entity, null);

				if (member.DependencyMode == MemberDependencyMode.Child &&
					member.ElementInfo.ElementType.IsListAlike())
				{
					type = member.ElementInfo.ElementType.ListAlikeMemberType();
					if (type != null && type.IsClass)
					{
						if (!meta.MemberChilds.ContainsKey(member.Name)) meta.MemberChilds.Add(member.Name, new List<object>());
						var childs = meta.MemberChilds[member.Name]; childs.Clear();
						var iter = member.ElementInfo.GetValue(entity) as IEnumerable;
						foreach (var item in iter) childs.Add(item);
					}
				}

				break;
			}

			return value;
		}
	}

	// ==================================================== 
	/// <summary>
	/// Maintains the relevant information of a type that has been extended to support virtual
	/// lazy properties. This class is intended for internal usage only.
	/// </summary>
	public class ProxyHolder
	{
		List<LazyProperty> _LazyProperties = new List<LazyProperty>();

		/// <summary>
		/// Initializes a new empty instance.
		/// </summary>
		internal ProxyHolder() { }

		/// <summary>
		/// Returns the string representation of this instance.
		/// </summary>
		/// <returns>A string containing the string representation of this instance.</returns>
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();

			sb.AppendFormat(ExtendedType != null ? ExtendedType.EasyName() : "<unknown>");

			if (LazyProperties != null && LazyProperties.Count != 0)
			{
				sb.Append(" ["); bool first = true; foreach (var lazy in LazyProperties)
				{
					if (first) first = false; else sb.Append(", ");
					sb.Append(lazy);
				}
				sb.Append("]");
			}

			return sb.ToString();
		}

		/// <summary>
		/// The extended type created to manage the getters and setter of the virtual lazy
		/// properties of the original base type.
		/// </summary>
		internal Type ExtendedType { get; set; }

		/// <summary>
		/// The collection of lazy properties for which this holder was created.
		/// </summary>
		internal List<LazyProperty> LazyProperties
		{
			get { return _LazyProperties; }
		}
	}

	// ==================================================== 
	/// <summary>
	/// Represents a virtual lazy property in an extended type. This class is intended for
	/// internal usage only.
	/// </summary>
	public class LazyProperty
	{
		/// <summary>
		/// Initializes a new empty instance.
		/// </summary>
		internal LazyProperty() { }

		/// <summary>
		/// The holder where this lazy property is defined.
		/// </summary>
		internal ProxyHolder Holder { get; set; }

		/// <summary>
		/// Returns the string representation of this instance.
		/// </summary>
		/// <returns>A string containing the string representation of this instance.</returns>
		public override string ToString()
		{
			return OriginalProperty == null ? string.Empty : OriginalProperty.Name;
		}

		/// <summary>
		/// The given name of this lazy property for identification purposes, or null if this
		/// instance is not initialized yet.
		/// </summary>
		internal string Name
		{
			get { return OriginalProperty == null ? null : OriginalProperty.Name; }
		}

		/// <summary>
		/// The original property this instance refers to.
		/// </summary>
		internal PropertyInfo OriginalProperty { get; set; }

		/// <summary>
		/// The getter for the original property.
		/// </summary>
		internal MethodInfo OriginalGetter { get; set; }

		/// <summary>
		/// The setter for the original property.
		/// </summary>
		internal MethodInfo OriginalSetter { get; set; }

		/// <summary>
		/// The field to hold if the proxied virtual lazy property has been loaded (completed) or
		/// not.
		/// </summary>
		internal FieldInfo LazyCompletedFlag { get; set; }

		/// <summary>
		/// The property on the extended type.
		/// </summary>
		internal PropertyInfo ExtendedProperty { get; set; }

		/// <summary>
		/// The getter of the property on the extended type.
		/// </summary>
		internal MethodInfo ExtendedGetter { get; set; }

		/// <summary>
		/// The setter of the property on the extended type.
		/// </summary>
		internal MethodInfo ExtendedSetter { get; set; }

		/// <summary>
		/// Access back to the source property on the extended type.
		/// </summary>
		internal PropertyInfo SourceBackProperty { get; set; }

		/// <summary>
		/// The getter to access back the source property on the extended type.
		/// </summary>
		internal MethodInfo SourceBackGetter { get; set; }

		/// <summary>
		/// The setter to access back the source property on the extended type.
		/// </summary>
		internal MethodInfo SourceBackSetter { get; set; }
	}
}
// ======================================================== 

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