Click here to Skip to main content
Click here to Skip to main content

A Better Loader for ORM Frameworks

, 10 Feb 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
Generate loader classes for faster operation using Reflection.Emit
Prototype output... exciting isn't it!

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:

// generate the loader for Portfolio
IObjectDataLoader<Portfolio> loader = 
	DataLoader.GenerateObjectDataLoader<Portfolio>(mappings);

// storage
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? Smile | :)

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)
{

  // get the property on T
  PropertyInfo propertyInfo = type.GetProperty
	(mapping.PropertyName, BindingFlags.Instance | BindingFlags.Public);

  // get the data type of this property
  string dataTypeName = propertyInfo.PropertyType.Name;

  // luckily, most of the methods on IDataRecord following 
  // the GetDataTypeName(int) name pattern
  // so we can concat the type name and get the corresponding 
  // method for that data type :-)
  MethodInfo dataRecordMethod = typeof(IDataRecord).GetMethod
		("Get" + dataTypeName, new Type[] { typeof(int) });

  ilGen.Emit(OpCodes.Ldarg_1);                                  // instance
  ilGen.Emit(OpCodes.Ldarg_2);                                  // record
  ilGen.Emit(OpCodes.Ldc_I4, mapping.FieldIndex);               // int
  ilGen.Emit(OpCodes.Callvirt, dataRecordMethod);               // GetDataTypeName(int)
  ilGen.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());    // T.set_XXXX(xxxx)
  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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Chris Stefano
Software Developer (Senior)
South Africa South Africa
I'm a developer specializing in software for the financial services industry particularly in the decision support area. Currently my specialty is in investment compliance.

Comments and Discussions

 
GeneralEmitHelper.cs PinmemberMarc Leger23-Apr-06 18:11 
GeneralNHibernate 1.0.1 does this IMHO Pinmembereltwo18-Feb-06 4:01 
NewsYou are right, but not the first PinmemberBoudino17-Feb-06 1:35 
GeneralRe: You are right, but not the first PinmemberBuu Nguyen9-Mar-07 12:00 
GeneralASP.Net caching Pinmemberjaschag14-Feb-06 8:32 
GeneralRe: ASP.Net caching PinmemberChris Stefano14-Feb-06 9:33 
GeneralLightweight code generation instead of full-blown emit PinmemberStealthyMark14-Feb-06 6:40 
Net 2.0 has not only brought generics, but also Lightweight code generation (LWC) which is very similar to emitting complete assemblies. Instead you emit code for just a method, which has optionally access to private members of a specified type, and obtain a delegate to call the method.
 
The main advantage is the lower memory consumption and more importantly, the generated code can be garbage collected. I think that would suit your purposes very well.
 
Have a look at System.Reflection.Emit.DynamicMethod.
 
Mark
GeneralRe: Lightweight code generation instead of full-blown emit PinmemberChris Stefano14-Feb-06 9:31 
QuestionPerformance impact? worth the deal? PinmemberQuentin Pouplard12-Feb-06 5:15 
AnswerRe: Performance impact? worth the deal? PinmemberChris Stefano12-Feb-06 21:14 
GeneralRe: Performance impact? worth the deal? PinmemberQuentin Pouplard13-Feb-06 8:54 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 10 Feb 2006
Article Copyright 2006 by Chris Stefano
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid