Creating an ORM in C# - Part 2

5/13/2009

This is another long one, but hopefully it wont completely bore you... So I'm a bit further now with the ORM. It's currently to the point where I'm creating classes, overriding properties, etc. And I've already run into bugs with the code that I've posted thus far. As such I have gone back for some fixes. Specifically I didn't realize that the expression tree would actually contain a node to convert the object from one type to an object type. But that's an easy fix. Past that I've also settled on a name for this thing, HaterAide. I chose this because everyone seems to talk about "hydrating" their objects, I hate dealing with data layers, and I saw one of the new "G" commercials when I was thinking up a name. Plus, everything else I thought up was taken.

Anyway, let's move on to some code (I'm going to post the current source code later in the post). Last time we talked about using reflection and expression trees in order to get information about the properties within a class and map them within our system. This time we're adding 3 more mapping types and talking about how to use Reflection.Emit in order to create classes that override properties.

So let's start with the mappings. In the last post, I created an ID and Reference mapping type. ID would be the ID of the object (note that I didn't make it unique as I want to allow composite IDs). Reference on the other hand deals with simple types (string, int, etc. in either a list or a single value). This time we're adding Map, ManyToOne, and ManyToMany to the ClassMapping class. Map deals with situations where it is a one to one mapping of another business object. So let's look at its code.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Linq.Expressions;
   6: using HaterAide.Interfaces;
   7:  
   8: namespace HaterAide
   9: {
  10:     public class ClassMapping<T> : IClassMap<T>, IAttributeMap
  11:     {
  12:         private void AddAttribute(Attribute Property, Expression<Func<T, object>> expression)
  13:         {
  14:             string[] SplitName = null;
  15:             if (expression.Body.NodeType == ExpressionType.Convert)
  16:             {
  17:                 Property.Name = expression.Body.ToString().Replace("Convert(", "").Replace(")", "");
  18:                 string[] Splitter = { "." };
  19:                 SplitName = Property.Name.Split(Splitter, StringSplitOptions.None);
  20:                 Property.Name = SplitName[SplitName.Length - 1];
  21:                 UnaryExpression TempUnary = (UnaryExpression)expression.Body;
  22:                 Property.Type = TempUnary.Operand.Type;
  23:             }
  24:             else
  25:             {
  26:                 Property.Name = expression.Body.ToString();
  27:                 string[] Splitter = { "." };
  28:                 SplitName = Property.Name.Split(Splitter, StringSplitOptions.None);
  29:                 Property.Name = SplitName[SplitName.Length - 1];
  30:                 Property.Type = expression.Body.Type;
  31:             }
  32:             Properties.Add(Property);
  33:         }
  34:  
  35:         #region IClassMap Members
  36:  
  37:         public void ID(Expression<Func<T, object>> expression)
  38:         {
  39:             Attribute _ID = new Attribute();
  40:             _ID.AttributeType = AttributeType.ID;
  41:             AddAttribute(_ID, expression);
  42:         }
  43:  
  44:         public void Reference(Expression<Func<T, object>> expression)
  45:         {
  46:             Attribute _ID = new Attribute();
  47:             _ID.AttributeType = AttributeType.Reference;
  48:             AddAttribute(_ID, expression);
  49:         }
  50:  
  51:         public void Map(Expression<Func<T, object>> expression, string MappedProperty, bool Cascade)
  52:         {
  53:             Attribute _ID = new Attribute();
  54:             _ID.AttributeType = AttributeType.Map;
  55:             _ID.MappedProperty = MappedProperty;
  56:             _ID.Cascade = Cascade;
  57:             AddAttribute(_ID, expression);
  58:         }
  59:  
  60:         public void ManyToOne(Expression<Func<T, object>> expression, string MappedProperty, bool Cascade)
  61:         {
  62:             Attribute _ID = new Attribute();
  63:             _ID.AttributeType = AttributeType.ManyToOne;
  64:             _ID.MappedProperty = MappedProperty;
  65:             _ID.Cascade = Cascade;
  66:             AddAttribute(_ID, expression);
  67:         }
  68:  
  69:         public void ManyToMany(Expression<Func<T, object>> expression, string MappedProperty, bool Cascade)
  70:         {
  71:             Attribute _ID = new Attribute();
  72:             _ID.AttributeType = AttributeType.ManyToMany;
  73:             _ID.MappedProperty = MappedProperty;
  74:             _ID.Cascade = Cascade;
  75:             AddAttribute(_ID, expression);
  76:         }
  77:  
  78:         #endregion
  79:  
  80:         #region IAttributeMap Members
  81:  
  82:         private List<Attribute> _Properties = new List<Attribute>();
  83:  
  84:         public List<Attribute> Properties
  85:         {
  86:             get { return _Properties; }
  87:             set { _Properties = value; }
  88:         }
  89:  
  90:         #endregion
  91:     }
  92: }

Map, as you can see above, contains two new values that need to be sent in. One is the MappedProperty variable.  It lets the system know what this object corresponds to on the other object. For instance if we have a class called Dog that contained a property called Owner (of type Person) and the Person class contained a property called MyDog (of type Dog), we need to know that those match up. So, on the Dog class we'd pass in "MyDog" for the MappedProperty variable. Note that if the other class doesn't contain a property that maps back, we'd leave this null. The other variable is Cascade. This determines whether or not to cascade deletes, inserts, updates, etc. when this class is updated. ManyToOne and ManyToMany work in similar fashion, just changing the attribute type to the appropriate value.

That's all there is to it really for adding those types. We still load the information the same way we just need extra information when it is created. The next portion that I want to show you is the use of Reflection.Emit.

For those of you that don't know what the Reflection.Emit namespace is, it contains classes that let you spit out MSIL (Microsoft Intermediate language) code, metadata, etc. It's usually only used if you're creating something like a scripting engine. However in our case we're going to use it to dynamically create classes that will use the business objects as base classes. In order to do this, we're going to create 4 classes, a class manager (which looks through the assembly, finds the ClassMapping classes, and stores the created Class objects), a class to define/create the classes, a class to override properties, and a class to define fields. So let's take a look at the ClassManager class first:

   1: /*
   2: Copyright (c) 2009 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.Linq;
  26: using System.Text;
  27: using System.Reflection;
  28: using System.Threading;
  29: using System.Reflection.Emit;
  30: #endregion
  31:  
  32: namespace HaterAide.Reflection
  33: {
  34:     /// <summary>
  35:     /// Class manager
  36:     /// </summary>
  37:     internal class ClassManager
  38:     {
  39:         #region Constructor
  40:  
  41:         /// <summary>
  42:         /// Constructor
  43:         /// </summary>
  44:         /// <param name="Assembly">Assembly Using</param>
  45:         public ClassManager(Assembly Assembly)
  46:         {
  47:             CreateAssembly();
  48:             LoadClasses(Assembly);
  49:             foreach (Type Key in Classes.Keys)
  50:             {
  51:                 object CreatedClass = Activator.CreateInstance(this[Key].DerivedType);
  52:                 PropertyInfo Info = this[Key].DerivedType.GetProperty("ID");
  53:                 Info.SetValue(CreatedClass, 1, null);
  54:                 this[Key].DerivedType.InvokeMember("ClearChanged0", BindingFlags.InvokeMethod, null, CreatedClass, null);
  55:             }
  56:         }
  57:  
  58:         #endregion
  59:  
  60:         #region Private Functions
  61:  
  62:         /// <summary>
  63:         /// Creates the assembly builder
  64:         /// </summary>
  65:         private void CreateAssembly()
  66:         {
  67:              AssemblyName Name=new AssemblyName("HaterAide0Classes");
  68:             AppDomain Domain=Thread.GetDomain();
  69:             Builder=Domain.DefineDynamicAssembly(Name, AssemblyBuilderAccess.Run);
  70:             Module = Builder.DefineDynamicModule("HaterAide0ClassesM");
  71:         }
  72:  
  73:         /// <summary>
  74:         /// Loads the classes from the assembly and maps the business object type to
  75:         /// the mapping class
  76:         /// </summary>
  77:         /// <param name="BusinessObjectsAssembly">Assembly containing the business objects</param>
  78:         private void LoadClasses(Assembly BusinessObjectsAssembly)
  79:         {
  80:             Type[] Types=BusinessObjectsAssembly.GetTypes();
  81:             foreach (Type TempType in Types)
  82:             {
  83:                 Type BaseType = TempType.BaseType;
  84:                 if (BaseType != null && BaseType.FullName.StartsWith("HaterAide.ClassMapping"))
  85:                 {
  86:                     Classes.Add(BaseType.GetGenericArguments()[0], new Class(TempType, Module));
  87:                 }
  88:             }
  89:         }
  90:  
  91:         #endregion
  92:  
  93:         #region Private Variables
  94:  
  95:         private Dictionary<Type, Reflection.Class> Classes = new Dictionary<Type, HaterAide.Reflection.Class>();
  96:         private AssemblyBuilder Builder=null;
  97:         private ModuleBuilder Module=null;
  98:  
  99:         #endregion
 100:  
 101:         #region Public Properties
 102:  
 103:         /// <summary>
 104:         /// Gets the associated class with the type
 105:         /// </summary>
 106:         /// <param name="value">Type value</param>
 107:         /// <returns>The generated class associated</returns>
 108:         public Reflection.Class this[Type value]
 109:         {
 110:             get
 111:             {
 112:                 return Classes[value];
 113:             }
 114:         }
 115:  
 116:         #endregion
 117:     }
 118: }

The ClassManager class was shown last time but you'll notice a number of changes. It still takes an assembly as input, but it adds a function (CreateAssembly). CreateAssembly is our first glimpse of Reflection.Emit. Within the function, we create an AssemblyName object, an AssemblyBuilder object, and a ModuleBuilder object. The AssemblyName object simply defines the assembly's name that the generated objects will reside within. The AssemblyBuilder is used to actually create the assembly and the ModuleBuilder is what we will be using to define the module (pretty straightforward considering their names). These objects have to be created prior to anything else, but really the only object that we're going to care about past this is the ModuleBuilder object.

The LoadClasses function looks fairly similar to the past version, but this time we're sending in the ModuleBuilder to the class object. That's really the only changes to the ClassManager class. The Class class though has a number of changes:

   1: /*
   2: Copyright (c) 2009 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.Linq;
  26: using System.Text;
  27: using System.Reflection.Emit;
  28: using System.Reflection;
  29: using HaterAide.Interfaces;
  30: #endregion
  31:  
  32: namespace HaterAide.Reflection
  33: {
  34:     /// <summary>
  35:     /// Used to override a class
  36:     /// </summary>
  37:     internal class Class
  38:     {
  39:         public Class(Type Class, ModuleBuilder Module)
  40:         {
  41:             Type BaseType = Class.BaseType;
  42:             Type GenericType = BaseType.GetGenericArguments()[0];
  43:             TypeBuilder TypeBuilder = Module.DefineType("HaterAide0ClassesM." + GenericType.Name + "Derived", TypeAttributes.Public, GenericType);
  44:             FieldBuilder ChangedField = CreateChangedFields(TypeBuilder);
  45:             CreateChangedClearMethod(TypeBuilder, ChangedField);
  46:             CreateConstructor(ChangedField, TypeBuilder, GenericType);
  47:             OverrideProperties(ChangedField, TypeBuilder, Class);
  48:  
  49:             _DerivedType = TypeBuilder.CreateType();
  50:         }
  51:  
  52:         private void CreateChangedClearMethod(TypeBuilder TypeBuilder, FieldBuilder ChangedField)
  53:         {
  54:             MethodBuilder Method = TypeBuilder.DefineMethod("ClearChanged0", MethodAttributes.Public, typeof(void), new Type[0]);
  55:             ILGenerator Generator = Method.GetILGenerator();
  56:             Generator.Emit(OpCodes.Ldarg_0);
  57:             Generator.Emit(OpCodes.Ldfld, ChangedField);
  58:             Generator.Emit(OpCodes.Callvirt, typeof(List<string>).GetMethod("Clear"));
  59:             Generator.Emit(OpCodes.Ret);
  60:         }
  61:  
  62:         private void OverrideProperties(FieldBuilder ChangedField, TypeBuilder TypeBuilder, Type ClassMapper)
  63:         {
  64:             IAttributeMap Item = (IAttributeMap)Activator.CreateInstance(ClassMapper);
  65:             foreach (Attribute Property in Item.Properties)
  66:             {
  67:                 if (Property.AttributeType == AttributeType.ID || Property.AttributeType == AttributeType.Reference)
  68:                 {
  69:                     Properties.Add(new Property(Property, TypeBuilder, ChangedField));
  70:                 }
  71:             }
  72:         }
  73:  
  74:         private FieldBuilder CreateChangedFields(TypeBuilder TypeBuilder)
  75:         {
  76:             FieldBuilder ValueField = TypeBuilder.DefineField("_ChangedList0", typeof(List<string>), FieldAttributes.Private);
  77:             PropertyBuilder Property = TypeBuilder.DefineProperty("ChangedList0", PropertyAttributes.None, typeof(List<string>), null);
  78:  
  79:             MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
  80:  
  81:             MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_ChangedList0", GetSetAttributes, typeof(List<string>), Type.EmptyTypes);
  82:             ILGenerator Generator = ValuePropertyGet.GetILGenerator();
  83:             Generator.Emit(OpCodes.Ldarg_0);
  84:             Generator.Emit(OpCodes.Ldfld, ValueField);
  85:             Generator.Emit(OpCodes.Ret);
  86:  
  87:             MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_ChangedList0", GetSetAttributes, null, new Type[] { typeof(List<string>) });
  88:             Generator = ValuePropertySet.GetILGenerator();
  89:             Generator.Emit(OpCodes.Ldarg_0);
  90:             Generator.Emit(OpCodes.Ldarg_1);
  91:             Generator.Emit(OpCodes.Stfld, ValueField);
  92:             Generator.Emit(OpCodes.Ret);
  93:  
  94:             Property.SetGetMethod(ValuePropertyGet);
  95:             Property.SetSetMethod(ValuePropertySet);
  96:  
  97:             return ValueField;
  98:         }
  99:  
 100:         private void CreateConstructor(FieldBuilder ChangedField, TypeBuilder TypeBuilder,Type GenericType)
 101:         {
 102:             ConstructorBuilder Constructor = TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
 103:             ILGenerator Generator = Constructor.GetILGenerator();
 104:             Generator.Emit(OpCodes.Ldarg_0);
 105:             Generator.Emit(OpCodes.Call, GenericType.GetConstructor(new Type[0]));
 106:             Generator.Emit(OpCodes.Ldarg_0);
 107:             Generator.Emit(OpCodes.Newobj, typeof(List<string>).GetConstructor(new Type[0]));
 108:             Generator.Emit(OpCodes.Stfld, ChangedField);
 109:             Generator.Emit(OpCodes.Ret);
 110:         }
 111:  
 112:         private List<Property> _Properties = new List<Property>();
 113:  
 114:         private Type _DerivedType = null;
 115:  
 116:         public Type DerivedType
 117:         {
 118:             get { return _DerivedType; }
 119:         }
 120:  
 121:         public List<Property> Properties
 122:         {
 123:             get { return _Properties; }
 124:         }
 125:     }
 126: }

You'll notice that the constructor has a couple of new functions and it creates a TypeBuilder object. The TypeBuilder is the class that actually defines the class. In our case we want a public class that derives from the business object (the GenericType object is the business object type). After the type is initially defined, it isn't created. We still have to define constructors, functions, etc. prior to calling the CreateType function.

Before we do anything, we need to define a Changed field, which will tell us if a property is assigned to (this is done in the CreateChangedFields function).  When defining your class, you can create Fields, Properties, and Methods (there are other things but that's what we care about). In the CreateChangedFields function, we start by creating a field (private list of strings to hold our list of changed variables). We also define a property and then have to define the properties get and set methods. Note that these methods (when defined by the compiler) will almost always start with get_ or set_ and then end with the name of the property. Anyway, within the get/set methods we get our first glimpse of op codes and IL generation.

IL is really nothing more than a series of op codes. Each op code does something (except of course nop). These op codes are generated by something called an ILGenerator. Every time you call Emit, you feed it an op code and the needed variables. In the ChangedList0 property all we're doing is setting/getting the list. So in our get function, we start with the Ldarg_0 op code. This op code loads whatever is at index 0 (usually it's the this variable). We then call Ldfld and feed in our field that we created. Ldfld simply loads a field and puts it on the evaluation stack. We then call Ret. Ret simply takes whatever is on top of the evaluation stack and returns it (in our case the field).

Now before you get worried about having to go and figure out every op code that you'll need for a function, there's a way to get that information from the system itself. First create a project, create a function/class/property that does the action that you want it to do. Then call ILDasm. ILDasm can be found here: C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe. You can even set it up to run from within Visual Studio. Simply create an external tool entry that takes the location of ILDasm as the command and "$(TargetPath)" as the arguments. That's all there is to it. So no worries on creating functions with Reflection.Emit, it's actually quite easy.

Anyway, the next function that is called is CreateChangedClearMethod. This just allows the system to clear the changed list. Next is the constructor. The reason that isn't created first is the fact that we have to actually create the changed list. The field, when defined, will default to null. So we need to set it in the constructor and as such we need to define the field first before we can instantiate it. The constructor first calls the base class's (our business object's) constructor, then creates a new list of strings, and finally stores it in the changed list field.

After the constructor we move on to the individual properties. We simply create an instance of the ClassMap and pull down the information about the properties. We then create Property objects and store them for later. The Property class is here:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Reflection.Emit;
   6: using HaterAide.Interfaces;
   7: using System.Reflection;
   8:  
   9: namespace HaterAide.Reflection
  10: {
  11:     internal class Property
  12:     {
  13:         public Property()
  14:         {
  15:         }
  16:  
  17:         public Property(Attribute Attribute, TypeBuilder TypeBuilder, FieldBuilder ChangedField)
  18:         {
  19:             Field = new Field(Attribute, TypeBuilder);
  20:             MethodAttributes GetSetAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Virtual;
  21:             MethodBuilder ValuePropertyGet = TypeBuilder.DefineMethod("get_" + Attribute.Name, GetSetAttributes, Attribute.Type, Type.EmptyTypes);
  22:             ILGenerator Generator = ValuePropertyGet.GetILGenerator();
  23:             Generator.Emit(OpCodes.Ldarg_0);
  24:             Generator.Emit(OpCodes.Ldfld, Field.FieldBuilder);
  25:             Generator.Emit(OpCodes.Ret);
  26:  
  27:  
  28:             MethodBuilder ValuePropertySet = TypeBuilder.DefineMethod("set_" + Attribute.Name, GetSetAttributes, null, new Type[] { Attribute.Type });
  29:  
  30:             Generator = ValuePropertySet.GetILGenerator();
  31:  
  32:             Generator.Emit(OpCodes.Ldarg_0);
  33:             Generator.Emit(OpCodes.Ldarg_1);
  34:             Generator.Emit(OpCodes.Stfld, Field.FieldBuilder);
  35:             Generator.Emit(OpCodes.Ldarg_0);
  36:             Generator.Emit(OpCodes.Ldfld, ChangedField);
  37:             Generator.Emit(OpCodes.Ldstr, Attribute.Name);
  38:             Generator.Emit(OpCodes.Callvirt, typeof(List<string>).GetMethod("Add"));
  39:             Generator.Emit(OpCodes.Ret);
  40:  
  41:             _Attribute = Attribute;
  42:         }
  43:  
  44:         private Field Field = null;
  45:  
  46:         private Attribute _Attribute;
  47:  
  48:         public Attribute Attribute
  49:         {
  50:             get { return _Attribute; }
  51:             set { _Attribute = value; }
  52:         }
  53:     }
  54: }

This one is similar to the Class object in that we're using ILGenerator to define methods. You will notice one change though. Namely we aren't defining a property. I talked about this issue before, but when you're overriding a property, you're really only overriding the get/set methods. Creating a property with the same name would cause the app to think that there were two properties with the same name, confuse it, and crash our program. Not what we want. Past that, everything should look familiar (with the exception of some op codes that were not used previously). One thing that may be tricky at first can be found in the set function. After storing the information in the field (which is actually created by the Field class) for the property, it goes on to call the Add function on the changed list. It starts off by loading the this variable, then the field, then the name of the property, prior to calling Add. The this variable has to be loaded first because otherwise it does not know from which object the field should be taken from. If it were a local variable, we wouldn't have to worry about it but it's a field so you have to load this first. That annoyed the heck out of me the first time that bug popped up.

And that's all there is to the Reflection portion of the ORM. We now have our classes, they can be created. We can save/get information from them (assuming we had the SQL portion set up). There are a couple minor changes/additions that will need to be added but since I'm going for something basic I'm happy. So download the code, leave feedback, and happy coding.



Comments