ASP.NET developers who don't know what SubSonic is should definitely go read about it. We have been using SubSonic since the initial 2.0 release and have built most of our recent data-driven applications using this wonderful tool. We really like SubSonic and think it has helped us tremendously in our daily productivity. For all simple SQL operations, we try to never write custom SQL and use the SubSonic Query object instead. What I want to talk about today is how we use SubSonic in our main project FranchiseBlast and how we think this could be useful for other programmers like us.
A bit of introduction is required; FranchiseBlast has a set of different web pages to manage our data (products, sale reports, pricing, web orders, etc.). Most of these pages use the typical master list / detailed view usage scenario, with the master list being filtered due to search criteria. Additionally, because FranchiseBlast includes fine-grained access control, all lists need to be filtered according to these permissions, which vary per user. Although there are many built-in constructs in ASP.NET (GridView, DetailsView, FormView, etc.) that support this scenario via an SqlDataSource, but let us explain why we prefer using SubSonic.
Most of the time, you don't want to rewrite the same code again and again. When doing a classic scenario of displaying a GridView in ASP.NET, you are faced with multiple things you have to handle. First, how do you do sorting? Do you do it in memory with a DataView, for example, or do you let SQL server do it? Writing the SQL to benefit from SQL Server sorting is more work, but if your table has a lot of data, then it becomes absolutely necessary.
What do you do for paging? Do you want to fetch ALL the rows from your database and then let the grid view handle the paging in memory? This works well until you have a table containing thousands of rows because your GridView only displays the first 15 rows and you're transferring the all that data from SQL Server to ASP.NET for nothing (that's if you didn't use caching, but that's of another subject).
Sorting and paging are only two problems. What about filtering with a search string? Filtering by user permissions? These are problems we wanted to solve once and for all at LavaBlast when creating our core data management engine. We did not want to copy paste complex SQL code everywhere to manage filtering, sorting, and paging, as this would cause a maintainability nightmare. We don't have millions of rows in our tables, but we have enough to cause perform issues if we don't think about scalability.
So what is the miracle solution? Here at LavaBlast, we think the solution is custom SubSonic controllers used with ASP.NET ObjectDataSource. We assume our readers already know how to use an ObjectDataSource and have a reasonable knowledge of SubSonic. So if you are not really familiar with ObjectDataSource, you should probably read this tutorial first: Displaying Data With the ObjectDataSource. And if you are not familiar with SubSonic, go to the web site and watch some SonicCasts or go read Rob Conery's blog (very interesting blog). Rob is one of the authors of SubSonic who just got hired by Microsoft to continue to work on the goodness of SubSonic! Nice work Rob!
Here is some sample source code that makes use of our custom Controller class. This class was manually authored and it adds a few methods to the Controller SubSonic generated for us by using the partial class mechanism available in C#. It returns a sorted list of item groups (a set of items) filtered by the user's permissions. Furthermore, it returns only the item groups to be displayed in the UI. We've extracted out the generic concepts of filtering, sorting, and paging. In the end, SubSonic generates a single SQL query that generates temporary tables in which the rows that are populated in the UI are displayed.
This controller is bound to an ObjectDataSource which feeds records into the GridView. FetchByProductAuthority has parameters to handle sorting, paging, and data filtering. "startRowIndex" and "maximumRows" are parameters used for paging. The "sort" parameter specifies the sort column name and the other three parameters are for filtering our data.
SubSonic connects to our SQL Server Database and uses code templates to generate ActiveRecord objects for all our tables, views, and stored procedures. Using strongly typed objects throughout our applications instead of ADO.NET DataRows greatly improves its maintainability for a negligible performance hit, since we only need a couple dozen objects to render one page of a GridView.
One of our ideas was to change the code generation templates to make all controllers derive from our base controller class. We made this class generic so that it could be used for all kinds of SubSonic objects and makes it easier to deal with object collections, also generated by SubSonic.
public abstract class SubSonicController<T, C>
where T : AbstractRecord<T>, new()
where C : AbstractList<T, C>, new()
We modified the code generation templates so that SubSonic controllers would extend our base controller:
public partial class ItemGroupController : SubSonicController<ItemGroup, ItemGroupCollection>
Since the class is partial, you can make a new file and continue to write methods for this controller in a completely separate file which won't be overwritten by SubSonic when you regenerate objects from the relational database. We could also have inherited from the generated controller to perform our extensions.
In any case, all our controllers have the same base class. This enables us to add functions to all our controllers. The most basic tasks you want to do with a controller is to do SQL sorting of your data and SQL paging. SubSonic can easily do the sorting and paging for you with a SubSonic Query object. So we decided to write custom function to adapt ObjectDataSource parameters to work with the SubSonic Query object. I include some code here to show how we do this:
protected static Query GetQueryByParams(string sort, int startRowIndex, int maximumRows)
Query q = CreateQuery();
q = AddPaging(q, startRowIndex, maximumRows);
q = AddSort(q, sort);
protected static Query AddSort(Query q, string sort)
q.OrderBy = OrderBy.Desc(sort.Split(' '));
q.OrderBy = OrderBy.Asc(sort.Split(' '));
protected static Query AddPaging(Query q, int startRowIndex, int maximumRows)
if (maximumRows > 0)
q.PageIndex = (int)(startRowIndex / maximumRows) + 1;
q.PageSize = maximumRows;
Therefore, when we want to construct a basic query which handles sorting and paging for us, all we have to do is to write this code: Query q = GetQueryByParams(sort, startRowIndex, maximumRows);. In addition, for those who are not yet familiar with the ObjectDataSource, it automagically provides the sort, startRowIndex, maximumRows according to what is clicked in the GridView. In summary, we don't have much code to write to obtain data efficiently from our database that respects our business processes (access control, auditing, etc.).
Furthermore, we added a custom method to handle searching through a list of columns: see the SearchFields property above. We simply define a List<string> of column names in which we wish to search and our base controller handles adding the required filters to the SubSonic Query object. Finally, we also added default sorting, which allows us to simply set the DefaultSort property in our controller to be used when no sort column is specified in the Fetch methods.
I'll stop here; I think this article is already WAY too long. I hope this can be useful to someone! We're currently considering upgrading the controller templates to automatically include methods like FetchByProductAuthority which are commonly used in FranchiseBlast, depending on the SQL Schema of the table. We could even generate the sort/search fields by using metadata stored in the database. Furthermore, we're very interested in generating some ASCX/ASPX files, following the architecture imposed by our solution, which would make use of our controllers. These generated files would be good starting points to cut down on development time for some of our pages.
I include the SubSonicController class if anyone would be interested.
SubSonicController.cs (4.22 kb)
As for the code template for SubSonic, just modify it to use this base class and pass the right types in the generic parameters.
public partial class <%=tbl.ClassName %>Controller : SubSonicController<<%=tbl.ClassName%>, <%=tbl.ClassName%>Collection>
If you have any questions or comments, don't hesitate to send them in.
Edit: It has come to my attention that I forgot to provide a disclaimer concerning the SearchFields in this post. SubSonic 2.0 does not fully support the OR query construct. Keep in mind that your query will not work properly if you specify more than one field name in the SearchField list PLUS you also use the AND query construct. (SubSonic uses boolean operator precedence, and not parenthesis.