Creating an ORM in C# Revisited – Part 1
This is a bit long but I feel that a bit of backstory is warranted here. For those unaware, a while ago I wrote my own ORM. It wasn't meant to really compete with the likes of nhibernate, etc. It was simply a learning project (what goes into an ORM, etc.). A while ago I started redoing parts and pieces to get better stability and speed (fixed a number of bugs, etc.). Once it got rather stable, I started to use it in some of my projects. At first it worked well, cut down on turn around time, etc. However I kept running into issues (mainly speed related). Eventually, on some of my larger projects, I had to strip out parts and write things by hand... I tried some of the other ORMs (nhibernate, subsonic, etc.) out there and ran into the same issues (although to a lesser extent as they were actually written by individuals that knew what they were doing). One thing was a constant though, no matter what I tried or which library I plugged in the speed issue would raise its ugly head. Eventually though, I ran into Massive.
Massive, written by Rob Conery, is what is called a micro ORM. It's very small (about 364 lines as of this writing) and rather fast (in my experience much faster than the other ORMs when I was testing it out). So back in February/March I start testing it out and instantly ran into a brick wall. It uses expando objects (which is a .Net 4 feature) and I'm stuck on .Net 3.5 for a number of my projects. So I start looking around for an alternative and couldn't find one. Instantly I thought, I know, I'll create my own! I have enough experience writing my first ORM, and since Massive is open source I can just look at that if I get into a corner in the design process. It will be simple! And then Zeus looked down from Olympus and said "Listen here you wanker, I'll show you what happens to people with too much hubris". By the way, I don't know why Zeus is British (maybe he's American but just likes watching a lot of BBC America, I don't know). It doesn't really matter, because the important part of the story is it was at this point that he sent down a lightening bolt and destroyed my laptop... Last laugh was on him as I needed a new one anyway so this just gave me an excuse.
So time passed, I got a new laptop, I loaded it up, etc. However I just, sort of, took a break from programming and just played some games on my 360 (replayed Dragon Age: Origins, played Dragon Age 2 [which was a bit of a disappointment in some ways], some expansions for Mass Effect 2 [which I actually liked more than the original], etc.). It was a rather peaceful break. Eventually though I decided to get going on this project. So I start and instantly find that someone already came out with a micro ORM that can do what I need... Dapper. Dapper, another micro ORM, was written by Sam Saffron and came in at about 1038 lines of code (as of this writing). Supposedly he created it for the same reasons that I had (speed issues), although part of me thinks it was to piss me off (even though I'm certain that he has no idea who I am or about my previous plan to write my own micro ORM since I didn't tell anyone else about it). All I could think at the time was how dare Mr. Saffron write a fantastic library that you should totally try out. So I decided even with his library being out there, I was going to still create my own because 1) I didn't learn anything from my encounter with Zeus and 2) Screw you Sam, screw you.
Unlike my previous series, this is probably going to be shorter for a couple of reasons. But basically it comes down to the fact that there are a number of things that we no longer need with this system (caching, etc.). Instead we just need a simple mapping mechanism (database to object and vice versa) and some way to communicate with the database. That's it, just two things (however I've broken it down into a couple classes, helper functions, etc. to keep things on the smaller side and hopefully simpler to follow). In fact it's so simple that I'm adding this directly to my utility library. Anyway, today I'm just going to talk about the mapping mechanism.
A while back I saw a project called AutoMapper. It seemed a bit of overkill to me at the time, but in this situation it was exactly the type of thing that I needed (basically something that would allow me to automatically copy from one location to another). However since I'm adding this directly to my utility library, I can't add AutoMapper (my number one rule with my utility library is I can't have outside dependencies other than .Net/windows libs. I'm even iffy about adding in any XNA/DirectX stuff). So I set out to recreate my own version of the code (and actually found a very easy way to do it).
Basically an object to object mapper is nothing more than a bunch of gets and sets saved away and then run when called. That's it. So the easiest way that I could think of to do this was to use Funcs and Actions (Func for the getting of the value, Action for the setting of the value). And since I didn't want to force the coder to supply a Func and Action for every property, I needed a way using Expression trees to create the appropriate items on the fly:
/// <summary>
/// Gets a lambda expression that calls a specific property's getter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <typeparam name="DataType">Data type expecting</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's getter function</returns>
public static Expression<Func<ClassType,DataType>> GetPropertyGetter<ClassType,DataType>(string PropertyName)
{
if (string.IsNullOrEmpty(PropertyName))
throw new ArgumentNullException("PropertyName");
string\[\] SplitName = PropertyName.Split(new string\[\] { "." }, StringSplitOptions.RemoveEmptyEntries);
PropertyInfo Property = Utilities.Reflection.Reflection.GetProperty<ClassType>(SplitName\[0\]);
ParameterExpression ObjectInstance = Expression.Parameter(Property.DeclaringType, "x");
MemberExpression PropertyGet = Expression.Property(ObjectInstance, Property);
for (int x = 1; x < SplitName.Length; ++x)
{
Property = Utilities.Reflection.Reflection.GetProperty(Property.PropertyType, SplitName\[x\]);
PropertyGet = Expression.Property(PropertyGet, Property);
}
if (Property.PropertyType != typeof(DataType))
{
UnaryExpression Convert = Expression.Convert(PropertyGet, typeof(DataType));
return Expression.Lambda<Func<ClassType, DataType>>(Convert, ObjectInstance);
}
return Expression.Lambda<Func<ClassType, DataType>>(PropertyGet, ObjectInstance);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's getter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <typeparam name="DataType">Data type expecting</typeparam>
/// <param name="Property">Property</param>
/// <returns>A lambda expression that calls a specific property's getter function</returns>
public static Expression<Func<ClassType, DataType>> GetPropertyGetter<ClassType, DataType>(PropertyInfo Property)
{
if (!IsOfType(Property.PropertyType, typeof(DataType)))
throw new ArgumentException("Property is not of the type specified");
if (!IsOfType(Property.DeclaringType, typeof(ClassType)))
throw new ArgumentException("Property is not from the declaring class type specified");
ParameterExpression ObjectInstance = Expression.Parameter(Property.DeclaringType, "x");
MemberExpression PropertyGet = Expression.Property(ObjectInstance, Property);
if(Property.PropertyType!=typeof(DataType))
{
UnaryExpression Convert=Expression.Convert(PropertyGet,typeof(DataType));
return Expression.Lambda<Func<ClassType, DataType>>(Convert, ObjectInstance);
}
return Expression.Lambda<Func<ClassType, DataType>>(PropertyGet, ObjectInstance);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's getter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's getter function</returns>
public static Expression<Func<ClassType, object\>> GetPropertyGetter<ClassType>(string PropertyName)
{
return GetPropertyGetter<ClassType, object\>(PropertyName);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's getter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <param name="Property">Property</param>
/// <returns>A lambda expression that calls a specific property's getter function</returns>
public static Expression<Func<ClassType, object\>> GetPropertyGetter<ClassType>(PropertyInfo Property)
{
return GetPropertyGetter<ClassType, object\>(Property);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's setter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <typeparam name="DataType">Data type expecting</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's setter function</returns>
public static Expression<Action<ClassType, DataType>> GetPropertySetter<ClassType,DataType>(string PropertyName)
{
if (string.IsNullOrEmpty(PropertyName))
throw new ArgumentNullException("PropertyName");
string\[\] SplitName = PropertyName.Split(new string\[\] { "." }, StringSplitOptions.RemoveEmptyEntries);
PropertyInfo Property = Utilities.Reflection.Reflection.GetProperty<ClassType>(SplitName\[0\]);
ParameterExpression ObjectInstance = Expression.Parameter(Property.DeclaringType, "x");
ParameterExpression PropertySet = Expression.Parameter(typeof(DataType), "y");
MethodCallExpression SetterCall = null;
MemberExpression PropertyGet = null;
if (SplitName.Length > 1)
{
PropertyGet = Expression.Property(ObjectInstance, Property);
for (int x = 1; x < SplitName.Length - 1; ++x)
{
Property = Utilities.Reflection.Reflection.GetProperty(Property.PropertyType, SplitName\[x\]);
PropertyGet = Expression.Property(PropertyGet, Property);
}
Property = Utilities.Reflection.Reflection.GetProperty(Property.PropertyType, SplitName\[SplitName.Length - 1\]);
}
if (Property.PropertyType != typeof(DataType))
{
UnaryExpression Convert = Expression.Convert(PropertySet, Property.PropertyType);
if (PropertyGet == null)
SetterCall = Expression.Call(ObjectInstance, Property.GetSetMethod(), Convert);
else
SetterCall = Expression.Call(PropertyGet, Property.GetSetMethod(), Convert);
return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
}
if (PropertyGet == null)
SetterCall = Expression.Call(ObjectInstance, Property.GetSetMethod(), PropertySet);
else
SetterCall = Expression.Call(PropertyGet, Property.GetSetMethod(), PropertySet);
return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's setter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <typeparam name="DataType">Data type expecting</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's setter function</returns>
public static Expression<Action<ClassType, DataType>> GetPropertySetter<ClassType, DataType>(PropertyInfo Property)
{
if (!IsOfType(Property.PropertyType, typeof(DataType)))
throw new ArgumentException("Property is not of the type specified");
if (!IsOfType(Property.DeclaringType, typeof(ClassType)))
throw new ArgumentException("Property is not from the declaring class type specified");
ParameterExpression ObjectInstance = Expression.Parameter(Property.DeclaringType, "x");
ParameterExpression PropertySet = Expression.Parameter(typeof(DataType), "y");
MethodCallExpression SetterCall = null;
if(Property.PropertyType!=typeof(DataType))
{
UnaryExpression Convert=Expression.Convert(PropertySet,Property.PropertyType);
SetterCall = Expression.Call(ObjectInstance, Property.GetSetMethod(), Convert);
return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
}
SetterCall = Expression.Call(ObjectInstance, Property.GetSetMethod(), PropertySet);
return Expression.Lambda<Action<ClassType, DataType>>(SetterCall, ObjectInstance, PropertySet);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's setter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's setter function</returns>
public static Expression<Action<ClassType,object\>> GetPropertySetter<ClassType>(string PropertyName)
{
return GetPropertySetter<ClassType, object\>(PropertyName);
}
/// <summary>
/// Gets a lambda expression that calls a specific property's setter function
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <param name="PropertyName">Property name</param>
/// <returns>A lambda expression that calls a specific property's setter function</returns>
public static Expression<Action<ClassType, object\>> GetPropertySetter<ClassType>(PropertyInfo Property)
{
return GetPropertySetter<ClassType, object\>(Property);
}
I don't have object creation or null checking in there, but it does do type conversion, etc. So not perfect, but it will do for our purposes. But basically I can use one of my other functions in my utility library:
/// <summary>
/// Gets the name of the property held within the expression
/// </summary>
/// <typeparam name="T">The type of object used in the expression</typeparam>
/// <param name="Expression">The expression</param>
/// <returns>A string containing the name of the property</returns>
public static string GetPropertyName<T>(Expression<Func<T, object\>> Expression)
{
if (Expression == null)
return "";
string Name = "";
if (Expression.Body.NodeType == ExpressionType.Convert)
{
Name = Expression.Body.ToString().Replace("Convert(", "").Replace(")", "");
Name = Name.Remove(0, Name.IndexOf(".") + 1);
}
else
{
Name = Expression.Body.ToString();
Name = Name.Remove(0, Name.IndexOf(".") + 1);
}
return Name;
}
/// <summary>
/// Gets a property name
/// </summary>
/// <typeparam name="ClassType">Class type</typeparam>
/// <typeparam name="DataType">Data type of the property</typeparam>
/// <param name="Expression">LINQ expression</param>
/// <returns>The name of the property</returns>
public static string GetPropertyName<ClassType, DataType>(Expression<Func<ClassType, DataType>> Expression)
{
if (!(Expression.Body is MemberExpression))
throw new ArgumentException("Expression.Body is not a MemberExpression");
return GetPropertyName(((MemberExpression)Expression.Body).Expression) + ((MemberExpression)Expression.Body).Member.Name;
}
private static string GetPropertyName(Expression expression)
{
if (!(expression is MemberExpression))
return "";
return GetPropertyName(((MemberExpression)expression).Expression) + ((MemberExpression)expression).Member.Name + ".";
}
And send that to GetPropertyGetter/Setter and you have the Expression trees that you need for getting/setting your properties. So let's look at how we can use this:
/// <summary>
/// Mapping class
/// </summary>
/// <typeparam name="Left">Left type</typeparam>
/// <typeparam name="Right">Right type</typeparam>
public class Mapping<Left, Right>
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
/// <param name="LeftExpression">Left expression</param>
/// <param name="RightExpression">Right expression</param>
public Mapping(Expression<Func<Left, object\>> LeftExpression, Expression<Func<Right, object\>> RightExpression)
{
string Name = Utilities.Reflection.Reflection.GetPropertyName(LeftExpression);
LeftGet = Utilities.Reflection.Reflection.GetPropertyGetter<Left>(Name).Compile();
LeftSet = Utilities.Reflection.Reflection.GetPropertySetter<Left>(Name).Compile();
Name = Utilities.Reflection.Reflection.GetPropertyName(RightExpression);
RightGet = Utilities.Reflection.Reflection.GetPropertyGetter<Right>(Name).Compile();
RightSet = Utilities.Reflection.Reflection.GetPropertySetter<Right>(Name).Compile();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="LeftGet">Left get function</param>
/// <param name="LeftSet">Left set action</param>
/// <param name="RightExpression">Right expression</param>
public Mapping(Func<Left, object\> LeftGet, Action<Left, object\> LeftSet, Expression<Func<Right, object\>> RightExpression)
{
this.LeftGet = LeftGet;
this.LeftSet = LeftSet;
string Name = Utilities.Reflection.Reflection.GetPropertyName(RightExpression);
RightGet = Utilities.Reflection.Reflection.GetPropertyGetter<Right>(Name).Compile();
RightSet = Utilities.Reflection.Reflection.GetPropertySetter<Right>(Name).Compile();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="LeftExpression">Left expression</param>
/// <param name="RightGet">Right get function</param>
/// <param name="RightSet">Right set function</param>
public Mapping(Expression<Func<Left, object\>> LeftExpression, Func<Right, object\> RightGet, Action<Right, object\> RightSet)
{
string Name = Utilities.Reflection.Reflection.GetPropertyName(LeftExpression);
LeftGet = Utilities.Reflection.Reflection.GetPropertyGetter<Left>(Name).Compile();
LeftSet = Utilities.Reflection.Reflection.GetPropertySetter<Left>(Name).Compile();
this.RightGet = RightGet;
this.RightSet = RightSet;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="LeftGet">Left get function</param>
/// <param name="LeftSet">Left set function</param>
/// <param name="RightGet">Right get function</param>
/// <param name="RightSet">Right set function</param>
public Mapping(Func<Left, object\> LeftGet, Action<Left, object\> LeftSet, Func<Right, object\> RightGet, Action<Right, object\> RightSet)
{
this.LeftGet = LeftGet;
this.LeftSet = LeftSet;
this.RightGet = RightGet;
this.RightSet = RightSet;
}
#endregion
#region Properties
/// <summary>
/// Left get function
/// </summary>
protected virtual Func<Left, object\> LeftGet { get; set; }
/// <summary>
/// Right get function
/// </summary>
protected virtual Func<Right, object\> RightGet { get; set; }
/// <summary>
/// Left set function
/// </summary>
protected virtual Action<Left, object\> LeftSet { get; set; }
/// <summary>
/// Right set function
/// </summary>
protected virtual Action<Right, object\> RightSet { get; set; }
#endregion
#region Functions
/// <summary>
/// Copies the source to the destination
/// </summary>
/// <param name="Source">Source object</param>
/// <param name="Destination">Destination object</param>
public virtual void Copy(Left Source, Right Destination)
{
RightSet(Destination, LeftGet(Source));
}
/// <summary>
/// Copies the source to the destination
/// </summary>
/// <param name="Source">Source object</param>
/// <param name="Destination">Destination object</param>
public virtual void Copy(Right Source, Left Destination)
{
LeftSet(Destination, RightGet(Source));
}
/// <summary>
/// Copies from the source to the destination (used in
/// instances when both Left and Right are the same type
/// and thus Copy is ambiguous)
/// </summary>
/// <param name="Source">Source object</param>
/// <param name="Destination">Destination object</param>
public virtual void CopyLeftToRight(Left Source, Right Destination)
{
RightSet(Destination, LeftGet(Source));
}
#endregion
}
This class is a simple class for copying A to B or vice versa (and it's generic enough that you can pass in your own Funcs/Actions (which will be important in the later stages). We have one special case if Left and Right are the same type and thus the special forms of copy but that's it. However this only copies one property to another, we still need something that copies multiple properties to their appropriate destinations:
/// <summary>
/// Maps two types together
/// </summary>
/// <typeparam name="Left">Left type</typeparam>
/// <typeparam name="Right">Right type</typeparam>
public class TypeMapping<Left, Right>
{
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public TypeMapping() { Mappings = new List<Mapping<Left, Right>>(); }
#endregion
#region Properties
/// <summary>
/// List of mappings
/// </summary>
protected virtual List<Mapping<Left, Right>> Mappings { get; set; }
#endregion
#region Functions
/// <summary>
/// Adds a mapping
/// </summary>
/// <param name="LeftExpression">Left expression</param>
/// <param name="RightExpression">Right expression</param>
public virtual void AddMapping(Expression<Func<Left, object\>> LeftExpression, Expression<Func<Right, object\>> RightExpression)
{
Mappings.Add(new Mapping<Left, Right>(LeftExpression, RightExpression));
}
/// <summary>
/// Adds a mapping
/// </summary>
/// <param name="LeftGet">Left get function</param>
/// <param name="LeftSet">Left set action</param>
/// <param name="RightExpression">Right expression</param>
public virtual void AddMapping(Func<Left, object\> LeftGet, Action<Left, object\> LeftSet, Expression<Func<Right, object\>> RightExpression)
{
Mappings.Add(new Mapping<Left, Right>(LeftGet, LeftSet, RightExpression));
}
/// <summary>
/// Adds a mapping
/// </summary>
/// <param name="LeftExpression">Left expression</param>
/// <param name="RightGet">Right get function</param>
/// <param name="RightSet">Right set function</param>
public virtual void AddMapping(Expression<Func<Left, object\>> LeftExpression, Func<Right, object\> RightGet, Action<Right, object\> RightSet)
{
Mappings.Add(new Mapping<Left, Right>(LeftExpression, RightGet, RightSet));
}
/// <summary>
/// Adds a mapping
/// </summary>
/// <param name="LeftGet">Left get function</param>
/// <param name="LeftSet">Left set function</param>
/// <param name="RightGet">Right get function</param>
/// <param name="RightSet">Right set function</param>
public virtual void AddMapping(Func<Left, object\> LeftGet, Action<Left, object\> LeftSet, Func<Right, object\> RightGet, Action<Right, object\> RightSet)
{
Mappings.Add(new Mapping<Left, Right>(LeftGet, LeftSet, RightGet, RightSet));
}
/// <summary>
/// Copies from the source to the destination
/// </summary>
/// <param name="Source">Source object</param>
/// <param name="Destination">Destination object</param>
public void Copy(Left Source, Right Destination)
{
foreach (Mapping<Left, Right> Mapping in Mappings)
{
Mapping.Copy(Source, Destination);
}
}
/// <summary>
/// Copies from the source to the destination
/// </summary>
/// <param name="Source">Source object</param>
/// <param name="Destination">Destination object</param>
public void Copy(Right Source, Left Destination)
{
foreach (Mapping<Left, Right> Mapping in Mappings)
{
Mapping.Copy(Source, Destination);
}
}
/// <summary>
/// Copies from the source to the destination (used in
/// instances when both Left and Right are the same type
/// and thus Copy is ambiguous)
/// </summary>
/// <param name="Source">Source</param>
/// <param name="Destination">Destination</param>
public void CopyLeftToRight(Left Source, Right Destination)
{
foreach (Mapping<Left, Right> Mapping in Mappings)
{
Mapping.CopyLeftToRight(Source, Destination);
}
}
#endregion
}
This class simple acts as a list type, allowing you to bundle up multiple mappings. But that's all that we need. We could go one more step up and have a manager that saves the various type mappings, but for the ORM it isn't needed. With this we have a simple way to set up some rather fast functions for copying data back and forth in our system. Next post I'll talk about the wrapper for the database connections and then in the last post, I'll show the actual ORM. So for now, take a look (or look ahead at my utility library as my code is pretty much finished and in there), and happy coding.
Sign up for More Posts
I have no idea why you'd put yourself through that but if you'd like more posts from me then how about using my RSS feed? Take a look.