Click here to Skip to main content
15,884,176 members
Articles / Programming Languages / C#
Alternative
Tip/Trick

Object Relational Mapping via Reflection

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
15 Jul 2011CPOL 15.6K   1   11
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)


Written By
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHandling Nullables Pin
Jörgen Andersson5-Nov-12 2:52
professionalJörgen Andersson5-Nov-12 2:52 
AnswerRe: Handling Nullables Pin
Kelqualyn5-Nov-12 7:20
Kelqualyn5-Nov-12 7:20 
GeneralRe: Yep. Using CodeDom compiler for such task probably will resu... Pin
Kelqualyn19-Jul-11 21:45
Kelqualyn19-Jul-11 21:45 
GeneralRe: Thanks heaps for updating the code, I understand what you ar... Pin
Simon Bridge19-Jul-11 17:56
Simon Bridge19-Jul-11 17:56 
Thanks heaps for updating the code, I understand what you are doing now: instead of using the set value method of the property-info object, you are building a delegate that actually calls the setter on the specified type. I'm not that great with lambda expressions yet, so I would probably have done it differently myself but it is a really good idea, thank you for sharing.
I guess the same thing could even be achieved by emitting IL or compiling procedurally generated c# code using the code-dom-compiler.

GeneralTry to convert step-by-step instead of direct conversion. Ev... Pin
Kelqualyn4-Aug-11 9:56
Kelqualyn4-Aug-11 9:56 
GeneralReal nice! I tried to implement type conversion to Enum type... Pin
simmerzeel3-Aug-11 4:20
simmerzeel3-Aug-11 4:20 
GeneralRe: Look at next post :-) Pin
Kelqualyn4-Aug-11 20:57
Kelqualyn4-Aug-11 20:57 
Generaldeleted Pin
Kelqualyn14-Jul-11 20:48
Kelqualyn14-Jul-11 20:48 
GeneralI really wanted to do a comparative test, running 1000 insta... Pin
Simon Bridge14-Jul-11 20:07
Simon Bridge14-Jul-11 20:07 
GeneralRe: To compile this code you need to target .NET Framework 4. Bu... Pin
Kelqualyn15-Jul-11 6:26
Kelqualyn15-Jul-11 6:26 
GeneralReason for my vote of 3 This does not even compile... which ... Pin
Simon Bridge14-Jul-11 20:04
Simon Bridge14-Jul-11 20:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.