IT Crossing
Monday, September 08, 2008 | Register | Login 
Minimize
 IT Crossing Blog
Jul 13

Written by: Don Worthley
7/13/2007 7:56 PM

I've been investigating an open source Data Access Layer(DAL)/Object Relational Mapper(ORM) lately that I really like.  It's based on the ActiveRecord pattern and it also provides strongly typed access to your stored procedures.  If you haven't seen it yet, you can check it out here.

You can optionally download and install a SubSonic Visual Studio Add-In which allows you to generate your entire DAL in a matter of minutes from right within Visual Studio.  I've been using SubSonic on a small project related to Recipes and after generating the SubSonic ActiveRecord objects, I had access to a Recipe class complete with great ActiveRecord features, like FetchAll and LoadByKey.

One of the things I wanted to do with my SubSonic objects was retrieve them at run time as some supertype and access some of the features available through a base class named AbstractRecord.  I didn't want to specify which kind of ActiveRecord (ActiveRecord derives from the AbstractRecord base class) object I was dealing with at compile time.  I wanted to determine this at runtime.

In effect, I didn't want to be forced to plan the details of my bar-b-q ahead of time, I just wanted to grab some SubSonic objects at the local SubSonic drive-through based on some kind of superclass that exposed the functionality I needed, which was basically just access to the GetColumnValue and SetColumnValue methods, both of which are defined in AbstractRecord.

The problem is that AbstractRecord is a Generic abstract class of the form AbstractRecord<T> where T : ActiveRecord<T>, new().  This means that if I want to pass instances of my specific AbstractRecord classes around, I'll have to at least identify ahead of time what specific type of AbstractRecord I'm talking about.  And that's more like a sit down restaurant than a drive-through window.

With all of this buzzing around in my head, I read this post by nayt at the forums for SubSonic.  Although I had already come up with a solution to the problem, I realized it wouldn't solve the problem in the post.  The issue raised in the post was that a BusinessDictionary object was needed with a generic (with a little g) indexer which would return an ActiveRecord object based off of some kind of key, most likely the name of the specific ActiveRecord class as a string.

As an aside, it would also be really nice to be able to write something like this:

DynamicActiveRecord dar = new DynamicActiveRecord(“Recipe”);

Where dar would provide access to generic versions of all the properties and methods that would make sense for an ActiveRecord object created at runtime for the Recipe Table, something akin to the un-typed DataSet.

But even if there isn't a dynamic ActiveRecord object available in SubSonic, it would seem that creating a new supertype just above AbstractRecord<T> would help address some of the issues we face when we try to retrieve ActiveRecord objects dynamically at runtime. A supertype that wasn’t generic would make it possible to make use of polymorphism more in our solutions. It would be wonderful, for example, if there were an interface named IAbstractRecord which defined all of the properties and methods that didn't rely on <T>. 

Anyway, I still think we can add a drive-through window façade on top of the SubSonic library, but we'll have to use reflection.  Here’s some code I worked up to see if how this idea would work:

//Code Reference:

// “SubSonicClasses” = Name of assembly containing my generated ActiveRecord classes

// “WTS” = Namespace of generated ActiveRecord classes.

// “Recipe” = Name of specific class to instantiate

// “CreatedDate” = Column from the Recipe table to be retrieved.

****Sample Code to Retrieve Column Value****

Object oActiveRecord = GetActiveRecordByClassName("SubSonicClasses","WTS", "Recipe");
LoadActiveRecordByKey(oActiveRecord, 0); //Load the data for a Recipe with recipeID=0.
string columnValue = GetActiveRecordColumnValue(oActiveRecord, "CreatedDate");

****End Sample Code****

****Static Methods Used in Sample****

private static Object GetActiveRecordByClassName(string assemblyName,
string namespacePrefix,
string className)
{
Assembly subsonicAssembly = Assembly.Load(assemblyName);
Type t = subsonicAssembly.GetType(namespacePrefix + "." + className);
Object oActiveRecord = Activator.CreateInstance(t);
return oActiveRecord;
}
private static void LoadActiveRecordByKey(Object oActiveRecord, Object key)
{
MethodInfo mi = oActiveRecord.GetType().GetMethod("LoadByKey",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
mi.Invoke(oActiveRecord, new Object[] { key });
}
private static string GetActiveRecordColumnValue(Object oActiveRecord, string columnName)
{
MethodInfo mi = oActiveRecord.GetType().GetMethod("GetColumnValue",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
Type[] genericType = new Type[] { typeof(string) };
MethodInfo gmi = mi.MakeGenericMethod(genericType);
Object retValue = gmi.Invoke(oActiveRecord, new Object[] { columnName });
return retValue.ToString();
}

****End Static Methods****

This code will only work if you have generated your SubSonic classes in a separate assembly. That means no build provider and no generating your classes inside of your App_Code directory. It also requires that you have the necessary permissions to use reflection. You’ll need to generate your classes inside of a separate project that you compile into an assembly that you can load with reflection. At least, that’s the only way I could get this to work while I was playing with it today. I created an assembly named SubSonicClasses and set a reference to this assembly in my test project.

You’ll notice when you use reflection to call the GetColumnValue that this method is defined to use Generics. This means that you’ll need to use the MakeGenericMethod to let the reflection engine know that you want to call the string version of GetColumnValue.

I realize that there are some obvious limitations to using reflection, like the fact that everything retrieved from GetColumnValue is retrieved as a string and cast as an object; but it does allow someone to use the SubSonic classes dynamically. I'm hoping that future versions of SubSonic will provide a more generic, Non-Generic supertype for AbstractRecord such as the IAbstractRecord described above as well as some kind of factory method which allows you to create an instance of one of your ActiveRecord objects by simply passing in the name of the Table or specific ActiveRecord class name.

If you haven't used SubSonic, check it out and let me know what you think.  If you are using it, I'd be interested to hear what you think so far.

Tags:

5 comment(s) so far...

I forgot to mention that I ran some quick tests to determine the performance hit for using reflection to load the ActiveRecord objects. I found that while there was a half second or so performance hit the first time the page was loaded, subsequent hits to the page showed only an additional 1 or 2 milliseconds. For most situations, this kind of overhead is negligible.

By Don on   7/15/2007 5:55 PM

Don thanks for this article! I've been doing a lot of work with reflection of late and I think, in terms of perf, you can cache the type that you've called into a static collection set, then search it first before calling the Assembly bits.

By Rob on   7/16/2007 6:13 AM

Thanks for the feedback, Rob! Feel free to add anything that you find useful.

By Don on   7/18/2007 2:49 AM

I'm not very smart, and don't get a lot of things but what exactly does this enable you to do ?

By Mischa on   7/28/2007 4:33 AM

Hi Mischa, Let's say you are writing a component that will show data from one of your SubSonic Active Record objects. This component won't be very reusable if you hard code the component to use the Customer class or the Customer Controller. What would be great is to have the ability to write the component to use any SubSonic object generically. This would be possible if there were some type of base interface that didn't use Generics which provided access to the functionality common to all active record objects. When I wrote the article, this wasn't available in SubSonic. When I started thinking more about how this could be implemented, I realized that reflection would be a great way to solve this problem and I was pleasantly surprised to find that there wasn't the performance penalty I thought there would be when using reflection. HTH, Don

By Don on   8/2/2007 4:38 PM

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Comment:
Add Comment   Cancel