Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Object Relational Mapping via Reflection

4.33/5 (2 votes)
15 Jul 2011CPOL 16.8K  
Same thing 10-100 times faster.Additionally, IndexMap can be built only once per DataTable.using System;using System.Collections.Generic;using System.Data;using System.Linq;using System.Linq.Expressions;using System.Reflection;namespace ReflectiveReader{ public static...
Same thing 10-100 times faster.
Additionally, IndexMap can be built only once per DataTable.

C#
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace ReflectiveReader
{
    public static class DataAccessExtensions
    {
        //Getting Convert.ChangeType method's MethodInfo and store in static field to access later.
        private static readonly MethodInfo ChangeType =
                typeof(Convert).GetMethods(BindingFlags.Public | BindingFlags.Static)
                    .Where(m => m.Name == "ChangeType")
                    .Select(m => new { Method = m, Parameters = m.GetParameters() })
                    .Where(p => p.Parameters.Length == 2)
                    .Where(p => p.Parameters[0].ParameterType == typeof(Object) && p.Parameters[1].ParameterType == typeof(Type))
                    .Select(p => p.Method)
                    .Single();
        /// <summary>
        /// Static class. Container for precompiled property-accessing delegates.
        /// </summary>
        /// <typeparam name="TReflectedType">Type, wich property-accessing delegates will be constructed on access to PropertySetters static property.</typeparam>
        private static class DataAccessExtensionReflectedType<TReflectedType> where TReflectedType : class
        {
            private static Dictionary<string, Action<TReflectedType, object>> _setters = null;
            private static readonly object SyncRoot = new object();
            //Property implemented using singletone pattern. Simplifies debugging. (Comparing with .cctor initialization)
            public static IDictionary<string, Action<TReflectedType, object>> PropertySetters
            {
                get
                {
                    if (_setters == null)
                    {
                        lock (SyncRoot)
                        {
                            if (_setters == null)
                            {//compile setters on first access
                                _setters = CompileSetters();
                            }
                        }
                    }
                    return _setters;
                }
            }
            /// <summary>
            /// Compiles delegates for setting properties of TReflectedType.
            /// </summary>
            /// <returns>Dictionary, contatning propertyname - delegate mapping.</returns>
            private static Dictionary<string, Action<TReflectedType, object>> CompileSetters()
            {
                var setters = new Dictionary<string, Action<TReflectedType, object>>();
                var type = typeof(TReflectedType);
                var objectType = typeof(object);
                //for each property in TRefelectedType
                foreach (var property in type.GetProperties())
                {
                    //that can be written
                    if (property.CanWrite)
                    {
                        var instance = Expression.Parameter(type, "instance");
                        var value = Expression.Parameter(objectType, "value");
                        //build lambda expression
                        var expression = Expression.Lambda(typeof(Action<TReflectedType, object>),
                                                            Expression.IfThen(
                                                                Expression.IsFalse(
                                                                    Expression.TypeIs(value, typeof(DBNull))),
                                                            Expression.Assign(
                                                                Expression.Property(instance, property),
                                                                    Expression.Convert(
                                                                        Expression.Call(null, ChangeType, value, Expression.Constant(property.PropertyType))
                                                                        ,property.PropertyType)
                                                            )
                                                            )
                                                           , instance , value);
                        //compile it into delegate
                        var @delegate = expression.Compile();
                        //store delegate into dictionary and associate with property's name
                        setters.Add(property.Name, (Action<TReflectedType, object>)@delegate);
                    }
                }
                return setters;
            }
        }
        /// <summary>
        /// Builds Dictionary, associating column id in given DataTable with corresponding property-accessing delegate.
        /// </summary>
        /// <typeparam name="TInstanceType">Type of instance to write values to.</typeparam>
        /// <param name="table">DataTable to read values from.</param>
        /// <returns>Comumn id to property accessing delegate mapping.</returns>
        public static IDictionary<int, Action<TInstanceType, object>> BuildIndexMap<TInstanceType>(this DataTable table) where TInstanceType : class
        {
            var indexMap = new Dictionary<int, Action<TInstanceType, object>>();
            //for each compiled property-accessing delegate and property name pair
            foreach (var pair in DataAccessExtensionReflectedType<TInstanceType>.PropertySetters)
            {
                //get corresponding column index by property name
                var index = table.Columns.IndexOf(pair.Key);
                //if found
                if (index >= 0)
                {
                    //add index and delegate to result dictionary
                    indexMap.Add(index, pair.Value);
                }
            }
            return indexMap;
        }
        /// <summary>
        /// Sets properties of specified instance from given DataRow using given ColumnIndex to delegate mapping.
        /// </summary>
        /// <typeparam name="TInstanceType">Type of instance to set values.</typeparam>
        /// <param name="instance">Instance to set values.</param>
        /// <param name="indexMap">ColumnIndex to property-accessing delegate mapping.</param>
        /// <param name="row">Row to read values.</param>
        public static void SetPropertiesFrom<TInstanceType>(this TInstanceType instance, IDictionary<int, Action<TInstanceType, object>> indexMap, DataRow row) where TInstanceType : class
        {
            foreach (var pair in indexMap)
            {
                pair.Value(instance, row[pair.Key]);
            }
        }
        /// <summary>
        /// Sets properties of specified instance from given DataRow. Builds ColumnIndex to property-accessing delegate mapping and uses it.
        /// </summary>
        public static void SetPropertiesFrom<TInstanceType>(this TInstanceType instance, DataRow row) where TInstanceType : class
        {
            SetPropertiesFrom(instance, BuildIndexMap<TInstanceType>(row.Table), row);
        }
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)