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.