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:
Comments (5)
Go Kurt! We wuvvv uuuu - don’t give up :).
Hi Kurt, your work is awesome. This solution is exactly what I’m looking for. Could you please fix the links for the sources. It would be great, if I could have a deep look in your solution…
Thx
Doh, stupid Monoport.
I updated the links to point directly to my SubVersion branch. This is all from the previous release of SubSonic, though, so it may not be worth using directly.
Once I get time, I’m planning to update my branch with the latest bits and possibly merge things back into the trunk.
Thx Kurt for the quick reply. The links are working now, but it seems, that you have changed a lot of classes. Its pretty tough to get the SubSonic project compiled with all that changes…
I will give a try though, because I really need such a solution.
Thx again
Yeah, there were quite a few changes. Your best bet is checking out the whole branch and building that. It builds pretty much as is.