This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
The A-Z of VBScript Data Dictionaries Dave Cline |
The scripting.dictionary object effectively patches the hole left in VBScript by the exclusion of collections. |
Performance. For my Active Server Pages,
I just want what everybody else wants:
screaming-fast performance. Peregrine-falcon-dive-out-of-the-clouds-and-grab-you performance. Of the top three goals of app developers (performance, maintainability, and speedy development), performance remains the most elusive.
One way to get blazing performance in code developed with Visual Basic® is to use collections. Collections simplify many storage needs, allowing you to easily add items to a set and enumerate them. But collections don't exist in VBScript. Not to worry. The scripting.dictionary, a high-performance data storage object that Microsoft includes with Active Server Pages, effectively patches the hole left in VBScript by the exclusion of collections. A note about ASPs. As with many of the objects designed for ASPs, the nature and functionality of the objects, methods, and properties are easy to understand and implement. But enterprise-level applications can't be seriously undertaken without complete comprehension on the coder's part of where and how these objects, methods, and properties are to be used. The scripting.dictionary stands as just such an object.
Server-side Memory Storage
|
<OBJECT RUNAT=Server SCOPE=Application ID=d_Country
PROGID="Scripting.Dictionary"></OBJECT>
<OBJECT RUNAT=Server SCOPE=Application ID=d_DeepWide
PROGID="Scripting.Dictionary"></OBJECT>
<OBJECT RUNAT=Server SCOPE=Application ID=d_10List
PROGID="Scripting.Dictionary"></OBJECT>
SUB Application_OnStart
dim mConn
set mConn = Server.CreateObject("ADODB.Connection")
mConn.Open "DSN=Dictionary","",""
BuildDictionary d_Country, "CountryCode", "SELECT * FROM Country", mConn
BuildDictionary d_DeepWide, "ID1,ID2,ID3", "SELECT * FROM DeepWide", mConn
BuildDictionary d_10List, "listID", "SELECT * FROM List", mConn
set mConn = nothing
END SUB
A quick note on disconnected ADODB recordsets. As of this writing, ADO 2.0 has yet to be made part of Internet Information Server (IIS) 3.0. (I'm staying with IIS 3.0 until a couple of good books describe in detail the hundred or more checkboxes buried within IIS 4.0, or until a client explicitly requests it.) Unfortunately, ADO 1.5 and below do not contain the JET/DAO equivalent commands FindFirst and FindNext. Until it does, there's no performance gain in having a recordset hang out in the ASP Application object for the storage of cross-referenced, quick-lookup data.
An Internal Look at scripting.dictionary
An ASP dictionary, or rather the scripting.dictionary object, derives its excellent performance from its internal structure. A hash table lives inside the dictionary. A hash table, as opposed to an array, is a fixed allocation of heap memory. Knowing the size up front allows you to divide or hash (chop into little pieces) this chunk of memory into a fixed set of pieces. Once you know how many pieces you have, you can build an algorithm (hashing function) which, when fed a key, returns an index to one of your memory locations, where you then insert your key:item pair (see Figure 2).
Figure 2: A Hash Table |
Once inserted into the chosen memory location, retrieval or deletion can be elegantly performed by sending the hashing function the desired key. This function then hashes the key into an index that represents the exact location of your key:value pair. Your data, sir. A hash table's algorithm and collision handling sets the stage for good to great performance. Collision is what happens when two keys hash to the same memory slot. The scripting.dictionary's response to hashed key collision is called chaining. Chaining effectively allows multiple hashed keys to share the same hash table location by extending that location via a linked list (kind of a smart array) that stores the keys of previous and next records. You can scroll forward and back in the linked list to find the exact key being searched for. This type of collision handling exceeds the performance of even a balanced binary search tree. An array is nothing more than a dynamic chunk of memory given indexes and a storage area for storing pointers to the array's variant data. There is no linking between array items, as in a linked list; there are only sequential subscripts. Locating a certain index is a matter of starting at the beginning of the memory area and looking at each subscript until you find the one which matches the key. This knowledge is useful in understanding how the dictionary object works, but it's not vital when actually using it. What is important is knowing when to use it. Further discussion of the scripting.dictionary will involve specific code designed to aid your implementation of server side memory storage. For method/property syntax of the dictionary object, consult Microsoft's online scripting resources at http://www.microsoft.com/scripting/.
Application-level Storage
|
|
Declaring an object like this imparts a certain spin on the code you will use to manipulate the object. You won't dim or set a local variant to reference the dictionary. You'll just use the name straightaway: |
|
Or even: |
|
This last variation is possible because Items is the default method on the dictionary object. One minor point: prior to a reference within an ASP-instantiated COM object, an application-level dictionary must be set to a temporary
variable. When this is done, your dictionary data will then be available through a reference to this temporary variable within your COM object. If you don't do this in your
ASP, there will be no way to access your dictionary in your COM object. One last gotcha: since the dictionary object is apartment threaded, and you don't want simultaneous editing of your apartment threaded objects, you should use Application.Lock before and Unlock after you edit an application level dictionary. OK, now on to the screaming.
A Dictionary's Internal Storage
|
|
This amazingly powerful code will load a two-dimensional array of n columns by x rows. Dictionaries, on the other hand, must be loaded manually and with a bit of work. But once they're loaded, look out! They have essentially two columns, the key and the item, and can be x rows long. So how do you store a multifield table in a dictionary? The method I used is to convert each row of fields into a single-dimension array and then store the array as the item, yielding, essentially, a collection of arrays. But what do you use as the key? They have to be uniqueduplicating dictionary keys will generate a runtime error. Take a look at the BuildDictionary routine in Figure 3. Note the parameters passed to this routine. Dict is the name declared in your Global.asa; strSQL is the Select for the recordset; mConn an ADODB.Connection, and strKeyField contains a comma-delimited string of field names that represent the index on the table to be loaded. Essentially, this means I used the table key stored as the dictionary key, guaranteeing uniqueness. I separated the fields with a bar after I ran into a set of keys which had values like "60, 6" and "6, 06". These, when concatenated, created duplicate dictionary keys, a definite no-no. The item as an array works out beautifully. Two VBScript methods, Join and Split, allow you to quickly manipulate each array. Additional development guidelines at my job require us to create constants for all data-set field positions and FetchDictionaryItem. Figure 4 demonstrates how these constants are put to good use. If you're balking at the complexity and seemingly heavy overhead of this memory storage practice, I refer you to the performance ASP I built expressly for testing this construction, and to the results of the performance tests in the sidebar. In coding more than two hundred of these types of collections, I've found I generally know a number of specifics (keys) required to find and show user data. For instance, if I have to run a stored procedure against a SQL database I typically will have to provide primary keys to return the data. The same is true for these server-side dictionaries. When I build the dictionaries, I know the primary keys, and when I feed routines such as FetchDictionaryItem like so: |
|
I get an almost instantaneous return of the membership policy for the key I fed: organization, club and user information. In the applications I've built this is handy and performance oriented.
More Developer Guidelines
|
|
These wrappers allow you to concentrate on the application, not on refiguring how dictionaries work. Attempting to build a complex (15 or more tables) ASP application without creating reusable modules and functions would be like shaking three 1000-piece puzzles together and then trying to assemble them blindfoldedyou may eventually get them assembled, but the results won't be very pretty. The idea here is to encapsulate and reuse. The dictionary.inc file contains everything I need to deal with any applications set of dictionaries. I've encapsulated all my required dictionary services within their own error-trapped routines. I can now reuse this in any application I desire. Months from now when I have to go back into the apps that use this modular include, I won't have a bit of hesitation as to how to pull an extra piece of information from one of my storage dictionaries.
Conclusion
|
From the June 1998 issue of Microsoft Interactive Developer.