| By nature, object collections are often populated all at once, such as when fetching a group of customers from a database. Using the traditional approach, you can instantiate a CCustomer object for each customer record: Set mobj_Items = New Collection
While Not rs_Customers.EOF
Set obj_Customer = New CCustomer
' Set obj_Customer Properties here
Call mobj_Items.Add(obj_Customer)
rs_Customers.MoveNext
Wend The performance implications aren't huge, but they're certainly noticeable if you need to instantiate a couple hundred customers. Meanwhile, the user probably isn't interested in all these customers, so many of your objects aren't even used. You can postpone instantiating an object until you need it. Internally, the collection can store the property values (state) for all the objects in a single data stream. When users request an object from the collection, you can extract and use the appropriate segment of data to populate a real object (see Figure 2). Create a Collection With XML
XML is a good candidate for storing your collection contents. Maintaining the collection and extracting appropriate segments of XML is probably similar for all custom collection classes. This article's sample application includes a class called CXMLCollection, which provides the functionality required of a collection class but differs from VB's collectionits contents are stored in a single XML string. (Download the project) You can build your custom collections, such as CCustomerCol, as wrappers for CXMLCollection. The wrapper class must manage the conversion between XML fragments in CXMLCollection and the real CCustomer objects: .gif) | Figure 2 | Store Collection Items as XML Click here.
| | ' Get the Customer from CXMLCollection
' as an XML string
ls_XML = mobj_Items.Item(av_Key)
' Instantiate a New CCustomer
Set obj_Customer = New CCustomer
' Populate the Object with the required Item
' state
Call obj_Customer.LetXML(ls_XML) CCustomer's LetXML method lets you populate all the object's properties using an XML segment. This raises an interesting point. You don't need to create a new instance of CCustomer every time users request an item from the collection. You can reuse the same instance, setting its state in response to each request. This crude form of object pooling causes some unexpected side effects: CustCol.Customer(1).Company = "LA"
MsgBox(CustCol.Customer(1).Company) The MsgBox statement accesses CustCol.Customer(1). This constitutes a new request for an item in the collection. CustCol gets the XML fragment for item 1 again to meet this request, overwriting the change in company name. You can fix this problem by having the collection recognize consecutive requests for the same object so it doesn't get XML from the collection unless a different object is requested. When you're finished setting the object's properties, you can indicate that you want to update the underlying XML: CustCol.Customer(1).Company = "LA"
CustCol.Customer(1).ContactName = "Sandy"
CustCol.Customer(1).Phone = "555-2312"
Call CustCol.Update This seems like a lot of unnecessary trouble because the standard VB Collection allows you direct access to its objects. However, being unable to reference objects in CustCol directly might be a blessing in disguise. A direct object reference allows you to change an object's properties without the collection being aware of the change. This is a problem if your collection tracks summary information or needs to know which items to save. Building a wrapper around the VB Collection object references actual objects in the collection: Public Property Get Customer _
(av_Key as Variant) as CCustomer
Set Customer = obj_Col.Item(av_Key)
End Property Once you return a reference to an object, you have no way of detecting whether the properties of that object are changed subsequently. Essentially, the caller has a direct line to the object in the VB Collection, and you're out of the loop. Trap and React to Changes
Invoking a method to commit your changes to CXMLCollection gives you an ideal opportunity to trap and react to changes. Your wrapper class's Update method calls CXMLCollection's Overwrite method to replace an existing XML fragment. CXMLCollection tracks changed items and marks the associated item as having changed when you invoke the Overwrite method: <CCustomer Key="AROUT" Changed="1"> This XML fragment shows the opening tag of a CCustomer object in CXMLCollection. It contains two attributes: Key and Changed. The Key attribute is the text key associated with the item, which lets you refer to an item either by position or using its key. The Changed attribute is zero by default and is set to 1 if the Overwrite method causes the item to change. The CXMLCollection object provides a method called ChangedItems that uses the Changed attribute to extract a subset of the collection's XML containing only items that have changed. This allows you to write a simple Save method in CCustomerCol that marshals only the changed items to the database tier: Call obj_CustColDB.SaveData _
(obj_Items.ChangedItems) Because CXMLCollection handles the changed items, you get this functionality automatically if you build your custom collection classes around it. Another benefit of using the CXMLCollection: the ease of marshaling the collection's contents between physically distributed tiers. Marshaling is the process of copying an object to a remote machine. If you attempt to pass an object to a remote machine, you actually pass a reference to the object, not the object itself. The result is that every reference use involves a trip across the network to the object's actual location. To marshal a standard collection of objects, you need to convert the collection into a format that can be marshaled. Unpack the marshaled data and instantiate all the objects again to re-create your collection on the receiving end. Fortunately, the contents of your XML-based collection are in a suitable format already. Send the XML string across the network, avoiding the need to pack and unpack your collection before and after marshaling. GetXML and LetXML properties are similar to those provided by CCustomer, and they facilitate extracting and setting the collection's entire contents. Even if you don't need to send the collection to a remote machine, the ability to copy it is a useful feature in itself. Copying a standard VB collection can be a tricky process. Because a collection is itself an object, you only hold a reference to it. If you attempt to use a simple assignment, you end up with two references pointing to the same collection: Set Col1 = Col2 You face this same problem when attempting to pass an object to a remote machine. Alternatively, if you attempt to add the objects from one collection to another, you're actually adding object references. So you end up with two distinct collections, both pointing to the same set of objects: For i = 1 to Col2.Count
Call Col1.Add(Col2.Item(i))
Next i To add objects using your XML-based collection, simply assign the XML of one collection to another: Col1.LetXML(Col2.GetXML) Work With Metadata
Metadata tells you something about your data's state, such as whether an object has changed. The Changed attribute is an example of metadatait doesn't refer to the real-world customer the way properties do, such as Name and Address. CCustomer uses metadata to indicate which of its properties contain Null. The class maintains an 11-character internal string in which each character represents one of the object's properties. Indicate that a property is Null by setting the appropriate character of the string to 1: Mid$(ms_Nulls, CUST_COMPANY, 1) = "1" The Nulls string gets attached to CCustomer's XML as an attribute in the same way that Key and Changed do. This example shows you that the CCustomer object's third and fifth properties are Null: <CCustomer Key="AROUT"
Changed="1"
Nulls="00101000000"> The performance issue alone might not be sufficient to justify using XML-based collections. But the ease of marshaling and copying objects, the ability to detect and handle changes automatically, and the ability to tag objects with metadata all suggest that it's a good idea to consider building a more intelligent kernel for your collection classes. If you can achieve this intelligence while maintaining or even improving performance, so much the better. Richard Dalton is a VB programmer with Des Cullen Software Ltd., where he develops applications for financial services companies in Ireland and the United Kingdom. When he's not writing code, he spends his time reading and writing about writing code. He sleeps occasionally. You can reach him by e-mail at hello@rdalton.com. |