How I Use SubSonic, Part 3: Caching and Query Auditing

In Part 1 I covered how I created a ControllerBase class that all my SubSonic queries run through. Once I had a central point for processing queries, I wanted to add two features. Request-length caching and the ability to see how long a query was taking to execute.

Adding Request-length Caching

First off, this will only work as shown with web applications due to a dependency on HttpContext.Items. You could easily modify this to work without this dependency.

The way my templates are structured gives me access to related objects through lazy-loaded properties on my domain objects. All of these properties use the Get(id) method on their respective Controller. Throughout the length of a single request, there’s no reason to retrieve a given entity from the database more than once. Using my CacheManager I added the following to the ControllerBase.Get() method:

public static ItemType Get(object id)
{
    if (id == null)
        return default(ItemType);

    CacheManager cache = new CacheManager(new RequestProvider());
    string cacheToken = string.Format("{0}_{1}", typeof(ItemType), id);

    ItemType item = cache.Get(cacheToken);

    if (item == null)
    {
        item = DB.Get(id);

        // subsonic returns a new item if a get by ID fails
        // but we want a null object
        if (item.IsNew)
        {
            return default(ItemType);
        }
        else
        {
            cache.Store(cacheToken, item);
        }
    }

    return item;
}

The cache is first checked for an existing object matching the type and ID. If it’s not found, the entity is retrieved from the database and stored in the cache. If it is found, the database hit is skipped. There’s also a section in there to handle how SubSonic deals with .Get() calls that don’t find a record. I prefer to return null in this case, but your preference may vary.

I don’t yet cache the results of collections, only single objects. Caching the results of collections is an intriguing problem that I may revisit in the future.

Query Auditing

After caching reduced the number of times I hit the database on each request, I wanted to find out how long individual queries were taking so I could optimize where needed. I also wanted a way to print out a list of every query called with the SubSonic-generated SQL.

First up was creating the Auditor class to handle creating and storing entries. I don’t want to spend time on the internals, so you can view the source if you want to get a sense of how it works. Briefly, the heart of the Auditor is the CreateEntry method that takes a SubSonic SqlQuery, Query or StoredProcedure and stores it for display later. There’s also Auditor.StartTiming() and Auditor.EndTiming() for determining how long the query took to execute. Each of the methods in ControllerBase that execute queries get the Auditor mixed in like so:

public static ListType GetCollection(SqlQuery q)
{
    Auditor.StartTiming();
    ListType coll = q.ExecuteAsCollection
();
    TimeSpan exTime = Auditor.StopTiming();

    Auditor.CreateEntry(q, Action.Fetch, coll.Count, exTime);

    return coll;
}

And the updated Get() method:

public static ItemType Get(object id)
{
    if (id == null)
        return default(ItemType);

    CacheManager cache = new CacheManager(new RequestProvider());
    string cacheToken = string.Format("{0}_{1}", typeof(ItemType), id);

    ItemType item = cache.Get(cacheToken);

    if (item == null)
    {
        Auditor.StartTiming();
        item = DB.Get(id);

        // subsonic returns a new item if a get by ID fails
        // but we want a null object
        if (item.IsNew)
        {
            Auditor.CreateEntry(cacheToken, Action.Fetch, 0, null, Auditor.StopTiming());
            return default(ItemType);
        }
        else
        {
            Auditor.CreateEntry(cacheToken, Action.Fetch, 1, null, Auditor.StopTiming());
            cache.Store(cacheToken, item);
        }
    }
    else
    {
        Auditor.CreateEntry(cacheToken, Action.LoadFromCache, 1, null, TimeSpan.Zero);
    }

    return item;
}

The Auditor stores the query stats in a List<Entry> in HttpContext.Current.Items. To display these entries, I wrote the following Webforms UserControl (have not yet migrated this to MVC).

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Auditor.ascx.cs" Inherits="AuditorControl" %>
<asp:Repeater ID="rItems" runat="server" Visible="false" OnItemDataBound="rItems_ItemDataBound">
    <HeaderTemplate>
        <table class="list" style="font-size: 11px;">
            <tr class="header">
                <th>Type</th>
                <th>Count</th>
                <th>Time</th>
                <th class="left">Description</th>
                <th class="left">Params</th>
            </tr>
    </HeaderTemplate>
    <ItemTemplate>
        <tr class="item">
            <td class="center top" style="width: 30px;"><%# Eval("Action") %></td>
            <td class="center top" style="width: 30px;"><%# Eval("ResultCount") %></td>
            <td class="center top" style="width: 30px;"><%# ((TimeSpan)Eval("ExecutionLength")).Milliseconds %>ms</td>
            <td class="top"><%# Eval("DescriptionHtml") %></td>
            <td class="top" style="width: 150px;">
                <asp:Repeater ID="rParams" runat="server">
                    <ItemTemplate>
                        <strong><%# Eval("Key") %>:</strong>
                        <span style="white-space: nowrap;"><%# Eval("Value") %></span><br />
                    </ItemTemplate>
                </asp:Repeater>
            </td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

And the code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using SubSonicAuditor;

public partial class AuditorControl : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Auditor.Entries.Count > 0)
        {
            rItems.DataSource = Auditor.Entries;
            // or to exclude cache retrieval:
            // rItems.DataSource = Auditor.Entries.Where(en => en.Action != Action.LoadFromCache);
            rItems.DataBind();
            rItems.Visible = true;
        }
    }

    protected void rItems_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
        {
            Repeater rParams = (Repeater)e.Item.FindControl("rParams");
            Entry entry = (Entry)e.Item.DataItem;

            rParams.DataSource = entry.Parameters;
            rParams.DataBind();
        }
    }
}

Put this user control on any page you want to test (or your site’s master page), make sure you’re running in Debug mode and you should get a list of all the queries executed and how long they took. Here’s a (heavily-blurred for security) example of the output.

Posted January 20th, 8:06 PM
Read more posts about .NET, SubSonic, Tips.

View Comments
Link

Updated Webforms Custom Selector for jQuery 1.3

In case you hadn’t heard, jQuery 1.3 was released yesterday. I haven’t had a chance to test out the custom selector I wrote for Webforms (I’m working almost exclusively with ASP.NET MVC right now), but Tim Banks left a comment to let me know it wasn’t working with 1.3. Tim took the time to fix the code, so here it is. Thanks Tim!

    jQuery.expr[":"].asp = function(a, i, m) {
        return jQuery(a).attr('id').endsWith(m[3]);
    };

UPDATE: Brian Grinstead has posted an additional update to fix an incompatibility with IE.

Posted January 15th, 10:15 PM
Read more posts about jQuery.

View Comments
Link

A different view of strings

If you use jQuery, I’m betting at some point you’ve needed to extract the database ID associated with an HTML element (e.g. <div id="customer_123">...</div>). How many times have you written code like this?

    $("div").click(function() {
        var attrValue = $(this).attr("id");
	var id = attrValue.substr(attrValue.indexOf("_")+1);
	// do something with the id
    });

This seemed like a perfectly reasonable approach to me for the longest time. While looking through the source of another site though, I saw something I hadn’t ever seen before. Apparently, JavaScript has a regex literal syntax that shortens the code above to the following:

    $("div").click(function() {
	var id = $(this).attr("id").split(/_/)[1];
	// do something with the id
    });

You don’t have to use the regex literal, a character or string will work too. Neither method is exceptionally readable, so I prefer to the shorter version. Either way works, so if you like one more than the other, use that. I’m not sharing this tip though because it’s novel though.

The reason I bring this up is because it demonstrated to me a totally different way of thinking about a really trivial problem. I saw a string of text like “edit_123” as a string to be parsed and the author of the code I learned this technique from viewed that string as a data structure with two parts. There’s a profound difference in thinking there.

Posted January 15th, 10:12 PM
Read more posts about Programming, jQuery.

View Comments
Link

Bulk renaming _svn folders to .svn

A long time ago I set TortoiseSVN to use _svn for the folder name instead of .svn because I had heard somewhere that .svn caused conflicts with Visual Studio or IIS (I can’t recall the exact details). While setting up my new computer, I didn’t want to apply this setting again because it’s not the default and there’s no conflict with recent versions of Visual Studio. After a short search, I found the following batch file which I modified slightly for my situation.

WARNING: Use at your own risk – Works on my machine™

FOR /R %%f IN (_svn) DO IF EXIST "%%f" (
     ATTRIB -h "%%f"
     RENAME "%%f" .svn
     ATTRIB +h "%%f"
)

Copy this into a new text file, save the file, and rename it to updatesvn.bat. Then, place this file in the folder that you want to update it and double click to run. It will update the folder it’s in and all subfolders.

The batch file looks for folders named _svn, removes the ‘Hidden’ attribute, renames the folder to .svn and adds the ‘Hidden’ attribute back.

Posted January 12th, 6:07 PM
Read more posts about Tips.

View Comments
Link

Using IIRF URL Rewriting on IIS6 with WordPress

This is a WordPress blog hosted on IIS6, which isn’t always the smoothest combination. One common WordPress feature not supported on IIS6 is clean URLs requiring permalinks to look like this: http://john-sheehan.com/blog/index.php/net-cheat-sheets/

I wanted to get rid of the ‘index.php’ from the URL and had some experience doing URL rewriting using the excellent open source project Ionic’s ISAPI Rewrite Filter. IIRF is a ISAPI extension you can install on your IIS6 web servers to achieve mod_rewrite (from Apache) style URL rewriting. ISAPI filters on IIS6 are invoked prior to a request being directed to ASP.NET or PHP, so it’s great for removing file extensions like .aspx and .php. WordPress runs almost everything through the index.php file, so it’s not that complicated to rewrite all requests to remove the page name from the URL. To setup IIRF, follow the instructions in the readme included with the download.

Here’s my rule file:

RewriteCond %{HTTP_HOST} ^(www\.john-sheehan\.com).*$ [I]
RedirectRule ^/(.*)$ http://john-sheehan.com/$1 [I,R=301]

RedirectRule ^/blog/index\.php/(.*)$ /blog/$1 [I,R=301]
RewriteRule ^/blog/(?!index\.php|wp-|xmlrpc)(.*)$ /blog/index.php/$1 [I,L]

The first two lines remove the www. from any requests. If you hit my blog with www. at the beginning of the URL, you’ll get a permanent redirect (301) to the same page without the www. The 301 redirect is to indicate to Google and other search engines that their indexes should be updated with the new location. The [I] flag specifies that it should do a case-insensitive match.

The third line takes care of issuing a 301 Redirect for all the existing links that include the index.php. I have a lot of backlinks out there and I obviously didn’t want any to break.

The last line has some serious regex-fu in it, so if you’re not familiar with regex, here’s a summary. The rule matches any incoming request that starts with /blog, not followed by index.php, wp- (we don’t want to rewrite any admin or content requests) or xmlrpc (if you exclude this, Windows Live Writer won’t work with your blog). The (.*) captures everything after /blog/. This capture is referred to in the second half of the rule with $1 that tells IIS which URL to serve up instead. The [L] flag tells IIRF to not process any more rules (preventing a loop where the rewritten URL would redirect forever).

Lastly, you’ll need to configure your WordPress Permalink structure like so:

image

Posted January 11th, 2:15 PM
Read more posts about Tips.

View Comments
Link

Blogkeeping

I do my best to avoid blogging about the blog itself, but there’s a few notable items.

The Lounge

Google AdSense is out, The Lounge is in. I’m pleased to be the newest member of The Lounge Advertising Network’s Small Publishers Room. I read a lot of the blogs in this room and I’m honored to be included amongst them. I’m going to have to step up the quality level to keep up with the others as there is a lot of quality content coming out of that group. Which leads me to the next item…

The Blog

I’ve decided to put a lot more emphasis on blogging this year. I set an obtainable goal (52 posts, not counting this one) and have been mulling over some ideas for generating more interesting posts about more than just .NET. I’ll be still blogging about the usual .NET web development stuff (especially ASP.NET MVC, my new BFF) but there will be more commentary on current technology issues. Hopefully you will find it interesting.

I also want to create some new cheat sheets and update the existing ones. The cheat sheets have been much more popular than I expected they ever would be. I’m glad people are finding them useful and I don’t want to let them languish.

I’ve also got a web site in the works that I’m hoping will become your new favorite site (not joking). If you like discussing and keeping up with the latest .NET stuff, you’re going to like this site. More on this to come. Once I get the first version up, I’ll talk about it more.

Lastly and fittingly, least importantly, the blog name has changed. Gone is the inside joke that only one person reading this blog would even get and in with the new, much more descriptive “John Sheehan : Blog”. Fancy huh?

Posted January 5th, 11:50 PM
Read more posts about Uncategorized.

View Comments
Link

My Projects

ManagedAssembly

RestSharp

jQuery Snippets for Visual Studio 2010

@dotnetlinks on Twitter

SnapLeague