In my quest to make SubSonic work for me, I've been stumbling through the source code and making improvements on my own super special branch. Of primary interest to me was the mechanism for loading objects related through a foreign key. A Review object might have a parent Author, accessible like so:
Review review = new Review(1);
Author author = review.Author;
The default SubSonic templates generate an accessor like this:
public Test.Author Author
{
get { return Test.Author.FetchByID(this.AuthorID); }
set { SetColumnValue("AuthorID", value.Id); }
}
This gets unwieldy in a hurry. Every single reference to the Author property on my Review class results in a database call. Code like this is scary:
for(int i = 0; i < 1000; i++)
{
Author a = review.Author;
}
There is a LazyLoad option which changes the generated code slightly, persisting foreign key relationships in a private variable when they're referenced. It's a good start, but doesn't help with something like this:
foreach(Review review in someReviewCollection)
{
Author a = review.Author;
}
In many applications, you might end up with thousands of reviews that share a small set of authors. Not only would that previous loop hit the database for every iteration, it might be reloading authors that have been seen before. This can quickly become a huge amount of load on a database server.
Yay delegates
The easiest fix for this problem would be to cache all the authors, and retrieve the cached instances when they're referenced through the Author property. Wouldn't it be nice to tell SubSonic "Hey, I want you to check the cache for all foreign key relationships in this object". Something like that would be possible with a foreign key property that looks like this:
public Test.Item Item
{
get { return LoadSingleObject<Item, int>(
this.ItemID,
test.Item.FetchByID
);
}
set { SetColumnValue("ItemID", value.Id); }
}
The above getter uses a new LoadSingleObject function to retrieve the related object. This function takes the related object's primary key value and a default function for retrieving that object.
For this to work, SubSonic needs some mechanism that allows us to hijack its relationship loading process. At a super high level, I attempted to make this possible by creating delegates for getting/storing individual objects, then keeping a list of those delegate pairs in each ActiveRecord object. When a foreign key property is referenced, it will run through that list looking for a hit with the "get" delegate (ie: a result that's not null). Assuming there are no hits, it will use the standard ActiveRecord.FetchById function to return the object. At that point, it iterates the list again and passed the found object back to the "store" delegate.
This has proven to be pretty flexible in my applications, and even provides a foundation for putting in eager loading at some point. When I cache my objects, I now use the RegisterSingleObjectLoader function on each of them to ensure that the cache is the first place they'll look when I reference one of their related objects.
You can have a look at my branch (/branches/kurt) to see exactly what I did. The lion's share of the changes are contained in these two files, which I've stuck on monoport so you can see them in all their syntax highlighted glory:

Posted in:
c#
,
code
,
delegates
,
performance
,
subsonic