Storing Recordsets and other objects in Application Scope with a free-threaded Dictionary Object

By Peter A. Bromberg, Ph.D.

Peter Bromberg

If you have a webserver that gets a lot of hits and you're hitting the database to retrieve the same data every time the same page loads to serve a new visitor, that's not going to scale well. And if you're going back to the database again for the SAME customer just because they've now hit a new page and you need the same data from the database all over again, well, that's just plain dumb!  I've been guilty of it -- it's just being lazy, that's all. You say, "Yeah, well when we have 10,000 hits a day, we'll re-write it".



You can cache frequently used data in Application scope easily and safely - provided you use a COM object that is free threaded. Microsoft have created a free- threaded "Lookup Table" object that they apparently use a lot, but after playing with it, I wasn't really that impressed. You can't really use it like an "Application scope session object", you have to load the data off the filesystem. However, there are a number of authors who have sample code or freeware "dictionary" objects that they've been kind enough to distribute without charge to the developer community at large. One of those I like is Caprock Consuting's free-threaded Dictionary object. Written in ATL and at only 44K, its well worth a look. Here's their URL:  http://www.caprockconsulting.com/comsoftware.asp , and I've included both a copy of the DLL and their excellent CHM helpfile in the download zip for this article.

What we're going to do here is show how to load a recordset into Application scope (using the Dictionary object of course) by serializing it into an XML string using the native Save, adPersistXML method and the ADO Stream object, and then retrieve the recordset from any ASP page and "walk the XML DOM" to get the data we need for that page.

First of course, register the component which will expose its ProgID, "caprock.dictionary". We'll instantiate the component into Application scope in our global.asa file using the OBJECT META Tag:

<OBJECT RUNAT=Server SCOPE=Application ID="Dict" ProgID="caprock.dictionary" ></OBJECT>

Now here's a page that retrieves an ADO recordset object, serializes it to a valid XML string, and stores it in the Application Dictionary object where it immediately becomes available to all the pages of your site. (We're gonna do this in Javascript, folks, no more VBScript for me, it died and went to heaven when .NET arrived.):

<%@Language="JScript" %>
<%

if(dict==null){
Application("dict")=Server.CreateObject("caprock.dictionary");
var dict=Application("dict");
}

var adOpenStatic = 3,adLockReadOnly = 1,adPersistXML = 1, adCmdText = 0x0001;
var sql = "SELECT Employeeid, lastname, firstname, title FROM employees order by lastname "
var Conn = Server.CreateObject("ADODB.Connection");
Conn.Open("Provider=SQLOLEDB.1;User ID=sa;Initial Catalog=northwind;Data Source=(local)");
var rstObj = Server.CreateObject("ADODB.Recordset");
try {
rstObj.Open(sql,Conn,adOpenStatic,adLockReadOnly,adCmdText);
if (rstObj.RecordCount > 0) {
var oStream = Server.CreateObject("ADODB.Stream");
oStream.Open;
rstObj.Save(oStream,adPersistXML);
oStream.Position = 0;
dict("recordset")=oStream.readText;

} else {
Response.Write("<root><b>No Records Found</b></root>");
}
}
catch (e) {
Response.Write("<root><b>Error: " + e.description + "</b></root>");
}

Response.write("Recordset is now stored in Application(\"dict\")");
%>
<BR>
<a href=updatexml.asp>Get dict object</a> from a new page, using Application("dct")
object.

So what we've done above is set our Application("dict") object (after making sure it exists) to the page variable "dict". We then proceed to create and open a recordset containing the results of some of the fields in the Employees table from the sample Northwind SQL Server database.

Then, we open an ADO Stream and Save the recordset to the stream object as an XML String. Finally, we store the XML recordset (which is now in the Stream) into the dictionary object: (dict("recordset")=oStream.readText). The recordset is now safely stored in application state, it won't lock our IIS down to the single thread, and it is available to every page and every user of our site, without ever hitting the database again! I hope this makes sense, cause I don't want my poor old SQL Server to work any harder than it really needs to, especially doing dumb things like loading the same old recordset again and again for every customer.

Now the "proof of concept" page, "updateXML.asp":

<%@Language="JScript" %>
<%
if(dict==null){
Application("dict")=Server.CreateObject("caprock.dictionary");
dict=Application("dict");
}

var xmlDoc=Server.CreateObject("MSXML2.DomDocument.3.0");
xmlDoc.loadXML(dict("recordset"));
// nail down the rs:data section which is where the records are stored in the MS ADO XDR schema
// and stick it in a nodeList that we can traverse...
var tempNode=xmlDoc.selectSingleNode("//rs:data");
var newNode=tempNode.childNodes;
var len = newNode.length;
var tmp;
var output;
// let's just build our own HTML table from scratch, walking the DOM...
output= "<table align="center" cellspacing="2" cellpadding="2" border="0" ><TR>";
for(var i=0;i<=len-1;i++){
tmp=newNode(i);
output+="<TD bgcolor=#ffcc66>" + tmp.getAttribute("firstname") + "</td>";
output+="<TD bgcolor=#ffcc66>" + tmp.getAttribute("lastname") + "</TD>";
output+="<TD bgcolor=#ffcc66>" + tmp.getAttribute("title") + "</TD></TR>";
}
output+="</TABLE>";
Response.write(output);
output = "";
%>

What we do above is (once again) make sure our "dict" Application variable exists, and then simply instantiate a new XML DomDocument object and use its LoadXML method to load the XML STRING which is held in the dict("recordset") dictionary key.

Next, we nail down the rs:data nodeList of our XML which is where the actual records live, and we kinda "walk the nodes" using a combination of DOM scripting and XPATH to build ourselves a nice little proof-of-concept HTML Table.

And that's how you can store gobs of data in Application state! You can store STATE lists for your drop-down listboxes, site configuration data, lists of links, and yes, even whole recordsets. You can take the extra time and load off your database, and make your site SCALE.

I guess it all boils down to the words of my grandmother, Liilian Bromberg (who lived to be 100):
"Peter Darling, if we were all meant to get along, there would be no people who wait to start writing their check until all the groceries are rung up."   Don't expect your webserver and database to do everything for you!

Happy programming.

 

 

Download the code that accompanies this article.

Peter Bromberg is an independent consultant specializing in distributed .NET solutionsa Senior Programmer / Analyst at in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com