Creating an ORM in C# - Part 1

Just as a warning, this is a long post. And before you groan and start running for the hills because someone mentioned building ANOTHER ORM (as if we need another one), note that this isn't meant to be something you would use in your own project. I've been working on this in my spare time as a side project to learn various features of C# 3.0 and .Net that I've never really gotten into before. And since this is a learning exercise, I decided to share my adventure with everyone else that's out there. Also keep in mind that this is not the best way to create an ORM. I'm certain that people who have successfully created one will read this and laugh at my poor attempt, but once again this is for learning purposes...
May 08 2009 by James Craig

First, if you don't know what an ORM is, read this.

Anyway, I might as well tell you my motivation. I mean I could pick any project under the sun to learn some of these concepts, classes, etc. in the framework. So why did I pick an ORM? The main reason was that I've yet to find an ORM that works the way that I want it to. NHibernate is close (and I do like the Fluent version), but it's still not quite what I want. That's it really. I just want something where I don't have to design my objects around it and it plugs in without too much of an issue (oh and no XML or Attributes littering my business objects).

Anyway, I've divided the ORM into four sections that I'll need to create:

  1. Reflection - I need to figure out what properties exist in an object, etc. And like I said above, I don't want to use attributes or XML. That leaves me using reflection and writing a couple classes to help get the information that I need and store it for later use.
  2. SQL Builder - This will actually create the tables, create the queries, etc. In this setup, I'm only dealing with SQL Server, but I'm going to build it using a provider model so that could be swapped out.
  3. Cache - To be honest, calling a database is a bit slow sometimes. So I need to create a mechanism to cache the information. I'm designing this only for web sites, but once again I'm going to try to build it in such a way that it could be swapped out.
  4. Settings - I need an easy way to configure this thing. So I need something simple to store that information.
    So really it's only 3 sections but I'm still going to count the settings... Anyway, in this post I'm going to talk about the reflection portion. And actually I'm not going to cover it completely in this post, just how to get the property information.

So I need some basic information from the properties of the business objects. Namely the name of the property, the base type, and the type of mapping that we want to use. So I'm using this class to hold that information:

public class Attribute
{
public Attribute()
{
}

private string _Name = "";
private Type _Type = null;
private AttributeType _AttributeType = AttributeType.ID;

public string Name
{
get { return _Name; }
set { _Name = value; }
}

public Type Type
{
get { return _Type; }
set { _Type = value; }
}

public AttributeType AttributeType
{
get { return _AttributeType; }
set { _AttributeType = value; }
}
}
public enum AttributeType
{
ID = 0,
Reference
}

Note that the attribute type defines only properties that are either the ID for the object or a reference (think simple types like string, int, etc. I'll add in classes, many to many mappings later). But so far this is pretty basic. On top of this, I'm not hard coding the business objects nor am I forcing them to inherit from an interface/base class. So I need a class so they can define the mapping with:

public interface IAttributeMap
{
List<Attribute> Properties { get; set; }
}

public interface IClassMap<T>
{
void ID(Expression<Func<T, object>> expression);
void Reference(Expression<Func<T, object>> expression);
}

public class ClassMapping<T> : IClassMap<T>, IAttributeMap
{
private void AddAttribute(Attribute Property, Expression<Func<T, object>> expression)
{
Property.Name = expression.Body.ToString();
string[] Splitter = { "." };
string[] SplitName = Property.Name.Split(Splitter, StringSplitOptions.None);
Property.Name = SplitName[SplitName.Length - 1];
Property.Type = expression.Body.Type;
Properties.Add(Property);
}

#region IClassMap Members

public void ID(Expression<Func<T, object>> expression)
{
Attribute _ID = new Attribute();
_ID.AttributeType = AttributeType.ID;
AddAttribute(_ID, expression);
}

public void Reference(Expression<Func<T, object>> expression)
{
Attribute _ID = new Attribute();
_ID.AttributeType = AttributeType.Reference;
AddAttribute(_ID, expression);
}

#endregion

#region IAttributeMap Members

private List<Attribute> _Properties = new List<Attribute>();

public List<Attribute> Properties
{
get { return _Properties; }
set { _Properties = value; }
}

#endregion
}

Ok, now the code above contains something you may have not seen before. Namely the Expression<Func<T,object>> expression part. You see in C# 3.0, an operator was added called the lambda operator. The lambda operator is used to create lambda expressions. Lambda expressions are anonymous functions that can contain statements, etc. and can be used to create expression trees or delegates. The portion of that, that we care about is expression trees.

An expression tree, which was introduced along with Linq, allows for translating executable code into data allowing us to modify the code before executing it... To be honest, that doesn't sound all that useful but it means that we can do things like this:

X=>X.PropertyName

And the property's information (such as name, type, etc.) would be available to us. And that's what we need, so that's what we're using. So how do we use the ClassMapping class?

public class MyClassMap : ClassMapping<MyClass>
{
public MyClassMap()
{
ID(x => x.ID);
Reference(x => x.TestProperty);
}
}

public class MyClass
{
public MyClass()
{
}

public virtual int ID { get; set; }

public virtual string TestProperty { get; set; }
}

The code above will look familiar to you if you've used Fluent NHibernate (I'm using their technique for defining the mappings). But the business object is MyClass. MyClassMap is the mapping class and inherits from ClassMapping. The reason we made ClassMapping generic was so we could do this. You'll notice that both the ID and Reference functions take in T type as the first portion. This allows us to define an object of the type that we define (in the example above, MyClass), and call its properties (which will end up in the body portion of the expression and we can get our information from there).

So we have our very basic mapping, we have classes to pull and hold the information that we need so now we just need something to find all of our mappings.

internal class ClassManager
{
#region Constructor

public ClassManager(Assembly Assembly)
{
LoadClasses(Assembly);
}

#endregion

#region Private Functions

private void LoadClasses(Assembly BusinessObjectsAssembly)
{
Type[] Types=BusinessObjectsAssembly.GetTypes();
foreach (Type TempType in Types)
{
Type BaseType = TempType.BaseType;
if (BaseType != null && BaseType.FullName.StartsWith("ClassMapping"))
{
Classes.Add(BaseType.GetGenericArguments()[0], new Class(TempType));
}
}
}

#endregion

#region Private Variables

private Dictionary<Type, Class> Classes = new Dictionary<Type, Class>();

#endregion
}

internal class Class
{

public Class(Type ClassMapping)
{
IAttributeMap Item = (IAttributeMap)Activator.CreateInstance(ClassMapping);
foreach (Attribute Property in Item.Properties)
{
//Here is where we'll add our property overridding at a later time
}
}
}

So there you go. Our ClassManager class uses reflection to find all classes that inherit from the ClassMapping class. In turn it creates a Class object (which will later override various properties and be used to create a derived type that we will use to actually store/load the information from the database). The Class object then creates an instance of the class mapping and can then get the information from it about the various properties.

So there's the beginning of my ORM. I'm certain that flaws will be found and things will change but this is what I have at the moment (actually I'm a bit further than this, but this is what I'm going to share for now). So take a look, leave feedback, and happy coding.