Introduction
It seems that there is an explosion of ORM frameworks available at the moment. I have looked at several of these, mostly the open source ones. I noticed that most of them are heavily reliant on reflection to load property values from the data source at runtime.
In "normal" types of applications, ORM frameworks provide an enormous productivity boost when dealing with lots of simple types of objects that need to be persisted from run to run. However, in my business domain, financial services, the volume of entities together with their complex inter-relationships make loading and saving objects a slow process.
I couldn't help wondering why there isn't an ORM framework out there (that I know of) which uses Reflection.Emit
to generate "loader" and "saver" classes at runtime, and thus remove the need for reflection.
Bring On ORMReflectionEmit
Now I don't want to write yet another ORM framework! So I've written a prototype to test out the idea of using Reflection.Emit
to generate a "loader" class at runtime for a given type. This "loader" could be employed by an ORM framework in its class creation and loading section.
I had thought about doing this before in .NET 1.1, but all that boxing, unboxing and type casting made me dizzy, but now that we have generics in .NET 2.0, the solution is a whole lot easier, more elegant and it runs faster too.
I started by designing a loader class which looks something like the following code:
class PortfolioDataLoader
{
public PortfolioDataLoader()
{
}
public void LoadData(Portfolio instance, IDataRecord record)
{
instance.Id = record.GetInt32(0);
instance.Code = record.GetString(1);
instance.Description = record.GetString(2);
instance.CurrentHolding = record.GetDecimal(3);
instance.DateOpened = record.GetDateTime(4);
}
}
Refactoring the loader class for generics, I created the IObjectDataLoader<T>
interface and implemented it on my loader class. Rewritten as follows:
public interface IObjectDataLoader<T>
where T: new()
{
void LoadData(T instance, IDataRecord record);
}
class PortfolioDataLoader: IObjectDataLoader<Portfolio>
{
public PortfolioDataLoader()
{
}
public void LoadData(Portfolio instance, IDataRecord record)
{
instance.Id = record.GetInt32(0);
instance.Code = record.GetString(1);
instance.Description = record.GetString(2);
instance.CurrentHolding = record.GetDecimal(3);
instance.DateOpened = record.GetDateTime(4);
}
}
Now all that's left to do is generate this class at runtime using Reflection.Emit
. In essence, the only part of the class that will be different for each given type is the implementation of the LoadData
method. Ultimately, I would like to use the loader as shown in the next code snippet:
IObjectDataLoader<Portfolio> loader =
DataLoader.GenerateObjectDataLoader<Portfolio>(mappings);
List<Portfolio> list = new List<Portfolio>();
using(SqlDataReader reader = cmd.ExecuteReader(...))
{
while(reader.Read())
{
list.Add( DataLoader.CreateInstance<Portfolio>(loader, reader) );
}
}
I've made a number of assumptions for the purposes of this prototype:
- The field order in which the data is selected and provided via the
SqlDataReader
be immutable so that the respective reader.GetXXXX(int)
method for each data type can be called using the field index instead of the name. This further speeds up the loading process, as a lookup of the index for each given field name is avoided.
- No
null
field values are expected. Lots more work to get this right, but it can be done.
- That the
IDataRecord
interface's GetXXXX(int)
methods are named using the pattern where XXXX
is the data type name of its return type.
- I've created a highly simplified mapping class to describe the mapping between a class's properties and the index used to retrieve the data out of the data reader.
Also, the prototype contains no integrity checking or error handling.
I must admit I "cheated" a little, since the Reflection.Emit
documentation is somewhat scarce, I hand-coded some example implementations and disassembled them using the "ildasm.exe" tool (provided with the .NET SDK) to see what IL was generated. I then worked backwards in my code generation code to produce the same IL. That's one way to learn, no? :-)
The DataLoader.GenerateObjectDataLoader<T>(ORMapping[])
method generates a loader class for the given type T
according to the mappings supplied. Most notably, the code to produce the LoadData
method implementation is as follows:
foreach (ORMapping mapping in mappings)
{
PropertyInfo propertyInfo = type.GetProperty
(mapping.PropertyName, BindingFlags.Instance | BindingFlags.Public);
string dataTypeName = propertyInfo.PropertyType.Name;
MethodInfo dataRecordMethod = typeof(IDataRecord).GetMethod
("Get" + dataTypeName, new Type[] { typeof(int) });
ilGen.Emit(OpCodes.Ldarg_1); ilGen.Emit(OpCodes.Ldarg_2); ilGen.Emit(OpCodes.Ldc_I4, mapping.FieldIndex); ilGen.Emit(OpCodes.Callvirt, dataRecordMethod); ilGen.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod()); ilGen.Emit(OpCodes.Nop);
}
Conclusion
It can be done! Now the challenge is to take an existing ORM framework and replace the loader bits, oh, and write the saver bits too.
History
- 10th February, 2006: Initial post