I attended one of Francesco Balena's (email@example.com) talks at VBits in the fall of 1999. He discussed a number of ways to improve performance in Visual Basic applications, which was very enlightening. Many of his suggestions are incorporated in this chapter and others. After his talk, I cornered him to get his opinion on the fastest way to pass data between tiers. I suggested user-defined types and delimited strings. He listened patiently like one of those mountaintop monks. He then proceeded to describe a fairly obscure (to me) technique—PropertyBag objects. While I knew that these were not sacks kept backstage at a burlesque theater, I had never used them (intentionally). He told me that the PropertyBag was implemented for the first time in Visual Basic 5.0 and improved in Visual Basic 6.0 to support "persistable" objects by exposing the Persistable class attribute.
Basically, a PropertyBag is an object that can work as a repository for any type of (simple) data, which includes all public creatable numeric and string data, but does not include arrays and collections. This means that you have to store individual array items one by one. It also means that you'll want to use this approach to pass single-row values, such as the output arguments fetched from a stored procedure.
The PropertyBag object exposes only two methods and one property:
Each element of data in the PropertyBag is named so you can reference it later, either reading or writing. Both methods accept an optional argument indicating a default value for the element.
Initially, the PropertyBag object was designed as a mechanism to store any of your control's properties that were set in the VB IDE. That is, if you open a Visual Basic form file in a text editor such as Notepad, you'll see text that you wouldn't normally see on the code window in your application. This text describes the form, the controls, and all of their settings. This is where the PropertyBag stores the property settings of your control, with any binary information being stored in the equivalent FRX file.
When you run the Visual Basic Addin Visual Basic 6.0 ActiveX Control Interface Wizard, it creates PropertyBag code for each property you define in your custom control. If you need more details on how these work, consult MSDN help. The topic "Storing properties using the PropertyBag object" is a good information source.
Similarly, you can make your properties persistent by trapping the Visual Basic WriteProperties event. This event occurs less frequently, usually when the client form is unloaded after a property has been changed within the IDE. Runtime property changes are obviously not stored in this way. Ordinarily, you would not want them to be persistent.
Okay, so how can we use a PropertyBag to return data from another tier? Up to this point it seems that all the PropertyBag is good for is to manage Visual Basic form-based controls. Well, consider that the documentation does not really say that you can write to the Contents property. Given that, what if you created a property bag on the server, filled it with a number of named property arguments (even in a hierarchy), and passed the Contents property as a byte array back to the client? Cool? Sure. And fast. However, this approach is not faster than passing back just the variables. On the client end, we simply take the Byte array and apply it to the Contents property of our own locally instantiated PropertyBag object—easy. Of course, those of you using ASP code will not be able to manage byte arrays…
Here's the server-side code. It's the same as the previous example except in this case, we pass a Byte array and construct a PropertyBag object to manage the row.
Public Function GetTitle(ISBNWanted As String) As Byte() Dim pbRetTitle As PropertyBag Set pbRetTitle = New PropertyBag Set cmd = New Command OpenConnection With cmd .Name = "TitleByISBN" .CommandText = "TitleByISBN" .CommandType = adCmdStoredProc .Parameters.Append _ .CreateParameter("ReturnStatus", adInteger, adParamReturnValue) .Parameters.Append _ .CreateParameter("ISBNWanted", adVarChar, adParamInput, _20, ISBNWanted) .Parameters.Append _ .CreateParameter("Publisher", adVarChar, adParamOutput, 20) .Parameters.Append _ .CreateParameter("City", adVarChar, adParamOutput, 50) .Parameters.Append _ .CreateParameter("Year_Published", adSmallInt, adParamOutput) .Parameters.Append _ .CreateParameter("Title", adVarChar, adParamOutput, 255) .Parameters.Append _ .CreateParameter("Cover", adVarChar, adParamOutput, 255) Set .ActiveConnection = cn cmd.Execute End With
As I said, pretty much the same as the other approaches. Now comes the magic. The following code simply writes each Recordset Field value to named PropertyBag elements. When it comes time to pass back the PropertyBag, we simply pass the Contents Byte array back.
With pbRetTitle .WriteProperty "ISBN", ISBNWanted .WriteProperty "Publisher", "" & cmd.Parameters(enuParms.Publisher) .WriteProperty "City", "" & cmd.Parameters(enuParms.City) .WriteProperty "Year_Published", "" & cmd.Parameters(enuParms.Year_Published) .WriteProperty "Title", "" & cmd.Parameters(enuParms.Title) .WriteProperty "Cover", "" & cmd.Parameters(enuParms.Cover) End With cn.Close GetTitle = pbRetTitle.Contents End Function
Here's the client code that accepts the data row back from the server. There's nothing complicated about sending Byte arrays over the wire, unlike some of the more sophisticated interfaces we have seen. However, it's more complicated, less maintainable, and harder to code. Moreover, you lose the ability to use this approach from script clients. In a sense, a PropertyBag is conceptually similar to passing XML strings around—both are extensible property repositories that allow for rich or dumb structure but pass on the burden of the extra handling to you. The only difference is the format of the persistence buffer.
Private Sub cmdQuery_Click() Dim pbLocalTitle As New PropertyBag Dim objLocalAuthors() As objAuthor Dim strGrid As String Set pbLocalTitle = New PropertyBag pbLocalTitle.Contents = GetTitle(ISBNList.Text) Image1.Picture = LoadPicture(pbLocalTitle.ReadProperty("Cover")) With pbLocalTitle strGrid = .ReadProperty("Publisher") & vbTab & _ .ReadProperty("City") & vbTab & _ .ReadProperty("ISBN") & vbTab & _ .ReadProperty("Year_Published") & vbTab & _ .ReadProperty("Title") & vbTab & _ .ReadProperty("Cover") End With With MSHFlexGrid1 .RowSel = 1 .ColSel = .Cols − 1 .Clip = strGrid .ColSel = 0 End With
Once the Byte array arrives from the server, applying it to a new PropertyBag object permits you to access each of the properties by name. It couldn't be much easier.