csharp/aadreja/vega/Vega/EntityCache.cs

EntityCache.cs
/*
 Description: Vega - Fastest ORM with enterprise features
 Author: Ritesh Sutaria
 Date: 9-Dec-2017
 Home Page: https://github.com/aadreja/vega
            http://www.vegaorm.com
*/

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace Vega
{
    /// 
    /// Cache of Ensaty
    /// 
    public static clast EnsatyCache
    {
        static ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
        static Dictionary Ensaties;

        /// 
        /// Delegate handler that's used to compile the IL to.
        /// (This delegate is standard in .net 3.5)
        /// 
        /// Parameter Type
        /// Return Type
        /// Argument
        /// Result
        public delegate TResult Func(T1 arg1);
        /// 
        /// This dictionary caches the delegates for each 'to-clone' type.
        /// 
        static Dictionary CachedCloneIL;

        static EnsatyCache()
        {
            Ensaties = new Dictionary();
            CachedCloneIL = new Dictionary();
        }

        /// 
        /// Clears all Ensaty cache. Can be used when switching database in runtime.
        /// 
        public static void Clear()
        {
            Ensaties.Clear();
            CachedCloneIL.Clear();
        }

        internal static TableAttribute Get(Type ensaty)
        {
            TableAttribute result;

            try
            {
                cacheLock.EnterReadLock();
                if (Ensaties.TryGetValue(ensaty, out result)) return result;
            }
            finally
            {
                cacheLock.ExitReadLock();
            }

            result = PrepareTableAttribute(ensaty);
            
            try
            {
                cacheLock.EnterWriteLock();
                Ensaties[ensaty] = result;
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }

            return result;
        }

        
        //to prepare TableAttribute 
        internal static TableAttribute PrepareTableAttribute(Type ensaty)
        {
            TableAttribute result = (TableAttribute)ensaty.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
            if (result == null)
            {
                result = new TableAttribute
                {
                    Name = ensaty.Name, //astuming ensaty clast name is table name
                    NeedsHistory = Config.NeedsHistory,
                    NoCreatedBy = Config.NoCreatedBy,
                    NoCreatedOn = Config.NoCreatedOn,
                    NoUpdatedBy = Config.NoUpdatedBy,
                    NoUpdatedOn = Config.NoUpdatedOn,
                    NoVersionNo = Config.NoVersionNo,
                    NoIsActive = Config.NoIsActive
                };
            }

            if (string.IsNullOrEmpty(result.Name)) result.Name = ensaty.Name;

            //find all properties
            var properties = ensaty.GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                //TODO: check for valid property types to be added in list
                if ((property.Name.Equals("keyid", StringComparison.OrdinalIgnoreCase) ||
                    property.Name.Equals("operation", StringComparison.OrdinalIgnoreCase)))
                    continue;

                //check for ignore property attribute
                var ignoreInfo = (IgnoreColumnAttribute)property.GetCustomAttribute(typeof(IgnoreColumnAttribute));
                var primaryKey = (PrimaryKeyAttribute)property.GetCustomAttribute(typeof(PrimaryKeyAttribute));
                var column = (ColumnAttribute)property.GetCustomAttribute(typeof(ColumnAttribute));


                if (column == null) column = new ColumnAttribute();

                if (string.IsNullOrEmpty(column.Name)) column.Name = property.Name;

                if (property.Name.Equals("CreatedBy", StringComparison.OrdinalIgnoreCase))
                    column.Name = Config.CreatedByColumnName;
                else if (property.Name.Equals("CreatedByName"))
                    column.Name = Config.CreatedByNameColumnName;
                else if (property.Name.Equals("CreatedOn"))
                    column.Name = Config.CreatedOnColumnName;
                else if (property.Name.Equals("UpdatedBy"))
                    column.Name = Config.UpdatedByColumnName;
                else if (property.Name.Equals("UpdatedByName"))
                    column.Name = Config.UpdatedByNameColumnName;
                else if (property.Name.Equals("UpdatedOn"))
                    column.Name = Config.UpdatedOnColumnName;
                else if (property.Name.Equals("VersionNo"))
                    column.Name = Config.VersionNoColumnName;
                else if (property.Name.Equals("IsActive"))
                    column.Name = Config.IsActiveColumnName;

                if (!column.IsColumnDbTypeDefined)
                {
                    if (column.Name.Equals(Config.CreatedByColumnName, StringComparison.OrdinalIgnoreCase) ||
                        column.Name.Equals(Config.UpdatedByColumnName, StringComparison.OrdinalIgnoreCase))
                        column.ColumnDbType = Config.CreatedUpdatedByColumnType;
                    else if (property.PropertyType.IsEnum)
                        column.ColumnDbType = TypeCache.TypeToDbType[property.PropertyType.GetEnumUnderlyingType()];
                    else if (property.PropertyType.IsValueType)
                        column.ColumnDbType = TypeCache.TypeToDbType[property.PropertyType];
                    else
                    {
                        TypeCache.TypeToDbType.TryGetValue(property.PropertyType, out DbType columnDbType);
                        column.ColumnDbType = columnDbType;
                    }
                }

                column.SetPropertyInfo(property, ensaty);

                column.IgnoreInfo = ignoreInfo ?? new IgnoreColumnAttribute(false);

                //Primary Key details
                if (primaryKey != null)
                {
                    column.PrimaryKeyInfo = primaryKey;

                    var virtualForeignKeys = (IEnumerable)property.GetCustomAttributes(typeof(ForeignKey));
                    if (virtualForeignKeys != null && virtualForeignKeys.Count() > 0)
                    {
                        if (result.VirtualForeignKeys == null) result.VirtualForeignKeys = new List();
                        result.VirtualForeignKeys.AddRange(virtualForeignKeys);
                    }
                }

                if (result.NoCreatedBy && (column.Name.Equals(Config.CreatedByColumnName, StringComparison.OrdinalIgnoreCase)
                    || column.Name.Equals(Config.CreatedByNameColumnName, StringComparison.OrdinalIgnoreCase)))
                    continue;
                else if (result.NoCreatedOn && column.Name.Equals(Config.CreatedOnColumnName, StringComparison.OrdinalIgnoreCase))
                    continue;
                else if (result.NoUpdatedBy && ((column.Name.Equals(Config.UpdatedByColumnName, StringComparison.OrdinalIgnoreCase)
                    || column.Name.Equals(Config.UpdatedByNameColumnName, StringComparison.OrdinalIgnoreCase))))
                    continue;
                else if (result.NoUpdatedOn && column.Name.Equals(Config.UpdatedOnColumnName, StringComparison.OrdinalIgnoreCase))
                    continue;
                else if (result.NoIsActive && column.Name.Equals(Config.IsActiveColumnName, StringComparison.OrdinalIgnoreCase))
                    continue;
                else if (result.NoVersionNo && column.Name.Equals(Config.VersionNoColumnName, StringComparison.OrdinalIgnoreCase))
                    continue;
                else
                {
                    if (!column.IgnoreInfo.Insert)
                        result.DefaultInsertColumns.Add(column.Name);

                    //isactive,createdon,createdby column shall not be included in default update columns
                    if (!column.IgnoreInfo.Update
                        && !column.Name.Equals(Config.IsActiveColumnName, StringComparison.OrdinalIgnoreCase)
                        && !column.Name.Equals(Config.CreatedByColumnName, StringComparison.OrdinalIgnoreCase)
                        && !column.Name.Equals(Config.CreatedOnColumnName, StringComparison.OrdinalIgnoreCase))
                        result.DefaultUpdateColumns.Add(column.Name);

                    if (!column.IgnoreInfo.Read)
                        result.DefaultReadColumns.Add(column.Name);

                    result.Columns[column.Name] = column;
                }
            }

            if(result.Columns.LongCount(p=>p.Value.IsPrimaryKey && p.Value.PrimaryKeyInfo.IsIdensaty) > 1)
            {
                throw new NotSupportedException("Primary key with multiple Idensaty is not supported on " + result.Name);
            }

            if (result.Columns.LongCount(p => p.Value.IsPrimaryKey) > 1 && result.NeedsHistory)
            {
                throw new NotSupportedException($"History for {result.Name} is not supported as it has composite Primary key");
            }

            return result;
        }

        //TODO: Remove this method Later as AuditTrail will be based on interface IAuditTrail
        //to prepare AuditTableAttribute 
        /* internal static TableAttribute PrepareAuditTrailTableAttribute()
        {
            TableAttribute result = new TableAttribute
            {
                Name = "audittrail",
                NeedsHistory = false,
                NoCreatedBy = false,
                NoCreatedOn = false,
                NoUpdatedBy = true,
                NoUpdatedOn = true,
                NoVersionNo = true,
                NoIsActive = true
            };

            var type = typeof(AuditTrail);

            foreach (PropertyInfo property in type.GetProperties())
            {
                var column = (ColumnAttribute)property.GetCustomAttribute(typeof(ColumnAttribute));
                column = column ?? new ColumnAttribute();

                if (property.Name == "AuditTrailId")
                    column.Name = "audittrailid";
                else if (property.Name == "OperationType")
                    column.Name = "operationtype";
                else if (property.Name == "TableName")
                    column.Name = "tablename";
                else if (property.Name == "RecordId")
                    column.Name = "recordid";
                else if (property.Name == "Details")
                    column.Name = "details";
                else if (property.Name == "RecordVersionNo")
                    column.Name = "recordversionno";
                else if (property.Name.Equals("CreatedBy", StringComparison.OrdinalIgnoreCase))
                    column.Name = Config.CreatedByColumnName;
                else if (property.Name.Equals("CreatedOn"))
                    column.Name = Config.CreatedOnColumnName;
                else
                    column.Name = property.Name;

                if (!column.IsColumnDbTypeDefined)
                {
                    if (column.Name.Equals(Config.CreatedByColumnName, StringComparison.OrdinalIgnoreCase))
                        column.ColumnDbType = Config.CreatedUpdatedByColumnType;
                    else if (property.PropertyType.IsEnum)
                        column.ColumnDbType = TypeCache.TypeToDbType[property.PropertyType.GetEnumUnderlyingType()];
                    else if (property.PropertyType.IsValueType)
                        column.ColumnDbType = TypeCache.TypeToDbType[property.PropertyType];
                    else
                    {
                        TypeCache.TypeToDbType.TryGetValue(property.PropertyType, out DbType columnDbType);
                        column.ColumnDbType = columnDbType;
                    }
                }

                column.SetPropertyInfo(property, typeof(AuditTrail));
                result.DefaultInsertColumns.Add(column.Name);
                result.DefaultReadColumns.Add(column.Name);

                if(property.Name == "AuditTrailId")
                {
                    column.PrimaryKeyInfo = (PrimaryKeyAttribute)property.
                        GetCustomAttributes(typeof(PrimaryKeyAttribute)).
                        FirstOrDefault();
                }

                result.Columns[column.Name] = column;
            }
            return result;
        }
        */

        #region clone object using IL

        /// 
        /// http://whizzodev.blogspot.com/2008/03/object-cloning-using-il-in-c.html
        /// Generic cloning method that clones an object using IL.
        /// Only the first call of a certain type will hold back performance.
        /// After the first call, the compiled IL is executed.
        /// 
        /// Type of object to clone
        /// Object to clone
        /// Cloned object
        internal static T CloneObjectWithIL(T ensaty)
        {
            Type mainType = typeof(T);

            if (!CachedCloneIL.TryGetValue(mainType, out Delegate cloneIL))
            {
                // Create ILGenerator
                DynamicMethod dymMethod = new DynamicMethod("DoClone", mainType, new Type[] { mainType }, true);
                ConstructorInfo cInfo = mainType.GetConstructor(new Type[] { });

                ILGenerator generator = dymMethod.GetILGenerator();

                LocalBuilder lbf = generator.DeclareLocal(mainType);
                //lbf.SetLocalSymInfo("_temp");

                generator.Emit(OpCodes.Newobj, cInfo); //create new instance of object
                generator.Emit(OpCodes.Stloc_0); //load in memory

                Type type = mainType;
                while (type != null && type != typeof(object))
                {
                    foreach (FieldInfo field in type.GetFields(BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.NonPublic))
                    {
                        // Load the new object on the eval stack... (currently 1 item on eval stack)
                        generator.Emit(OpCodes.Ldloc_0);
                        // Load initial object (parameter)          (currently 2 items on eval stack)
                        generator.Emit(OpCodes.Ldarg_0);
                        // Replace value by field value             (still currently 2 items on eval stack)
                        generator.Emit(OpCodes.Ldfld, field);
                        // Store the value of the top on the eval stack into the object underneath that value on the value stack.
                        //  (0 items on eval stack)
                        generator.Emit(OpCodes.Stfld, field);
                    }
                    type = type.BaseType;
                }
                

                // Load new constructed obj on eval stack -> 1 item on stack
                generator.Emit(OpCodes.Ldloc_0);
                // Return constructed object.   --> 0 items on stack
                generator.Emit(OpCodes.Ret);

                cloneIL = dymMethod.CreateDelegate(typeof(Func));
                CachedCloneIL.Add(mainType, cloneIL);
            }
            return ((Func)cloneIL)(ensaty);
        }

        #endregion

    }
}