Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / MSIL

A Better Loader for ORM Frameworks

Rate me:
Please Sign up or sign in to vote.
3.50/5 (4 votes)
10 Feb 2006CPOL3 min read 61.4K   279   34   11
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:

C#
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:

C#
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:

C#
// 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? :-)

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:

C#
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)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Systems builder and developer, DotNet C#, Ruby, Golang, Linux, Cloud, Open Source contributor.

Comments and Discussions

 
GeneralEmitHelper.cs Pin
Marc Leger23-Apr-06 17:11
Marc Leger23-Apr-06 17:11 
GeneralNHibernate 1.0.1 does this IMHO Pin
eltwo18-Feb-06 3:01
eltwo18-Feb-06 3:01 
NewsYou are right, but not the first Pin
Boudino17-Feb-06 0:35
Boudino17-Feb-06 0:35 
GeneralRe: You are right, but not the first Pin
Buu Nguyen9-Mar-07 11:00
Buu Nguyen9-Mar-07 11:00 
GeneralASP.Net caching Pin
jaschag14-Feb-06 7:32
jaschag14-Feb-06 7:32 
GeneralRe: ASP.Net caching Pin
Chris Stefano14-Feb-06 8:33
Chris Stefano14-Feb-06 8:33 
GeneralLightweight code generation instead of full-blown emit Pin
StealthyMark14-Feb-06 5:40
StealthyMark14-Feb-06 5:40 
GeneralRe: Lightweight code generation instead of full-blown emit Pin
Chris Stefano14-Feb-06 8:31
Chris Stefano14-Feb-06 8:31 
QuestionPerformance impact? worth the deal? Pin
Quentin Pouplard12-Feb-06 4:15
Quentin Pouplard12-Feb-06 4:15 
AnswerRe: Performance impact? worth the deal? Pin
Chris Stefano12-Feb-06 20:14
Chris Stefano12-Feb-06 20:14 
GeneralRe: Performance impact? worth the deal? Pin
Quentin Pouplard13-Feb-06 7:54
Quentin Pouplard13-Feb-06 7:54 

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

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