Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Better Loader for ORM Frameworks

0.00/5 (No votes)
10 Feb 2006 1  
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? :-)

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here