Property Bags
Property bags have the following characteristics:
-
Performance: Good
-
Object-Oriented: Yes
-
Self-Defined: No
Using property bags as a transport emerged along with the inclusion of object persistence in Visual Basic 6.0. Property bag objects can contain an entire instance of any object and convert that object to a byte array. The byte array can than be moved across process boundaries as a return value from a function. Once it's received by a calling client, the byte array is fed back into a property bag to re-create the object.
In many ways, using a property bag is the same as using MSPersist to pass Recordsets across process boundaries. The difference is that a property bag can convert any object into a byte array. The attraction of this approach is that you can build and pass custom objects like customer, order, and supplier objects. Because you can actually pass objects that represent business entities, many developers feel this approach is the closest to a pure OO application; however, it can be cumbersome to program and somewhat inflexible.
New Versus CreateObject
When using the property bag approach, you might often
find that you are creating instances of custom objects within the same DLL.
This raises the issue of whether to use the New keyword or the CreateObject
function when instancing your objects. This issue has implications for all
objects in your system, so I discuss it here.
Although using the New keyword in a COM+ application is acceptable,
you must be aware of some limitations. When you create instances with the
New keyword and the creator is in the same DLL as the class being
instantiated, Visual Basic creates the instance through an optimized process
known only to VB. This is a problem because COM+ can't set up properly when
instances are created this way.
The New keyword can be used anytime when the creator is in a different
DLL from the class being instantiated. In this case, the New keyword
evokes the normal creation process and COM+ works just fine. I have seen many
developers misunderstand these limitations. In some cases, development teams
have gone through their entire application and replaced New with
CreateObject. My experience, on the other hand, is that well-designed
distributed systems generally have no need to create instances of classes
within the DLL. They generally are looking for services from a separate component.
Nonetheless, be aware of this limitation and watch for it in code reviews.
Before you can even begin to use this transport mechanism, you must understand how to achieve object persistence in Visual Basic. Beginning with Visual Basic 6.0, you could create a persistent object by setting the Persistable property in a class module. The typical scheme is to create a class module that maps to a business entity and then make the class persistable.
When a class module is designated as persistable, Visual Basic adds three new events to the class: InitProperties, ReadProperties, and WriteProperties. The class is also now associated with a PropertyBag object that enables the persistence. The idea here is to save the values from a recordset into the class properties.
The PropertyBag object can hold the values of the properties in the class to be persisted. To store values in the PropertyBag, the class must explicitly read and write them to the bag. Reading and writing properties must occur in the ReadProperties and WriteProperties events. The code in Listing 4 shows how a class module representing an author might manage properties with a PropertyBag object.
Listing 4: Managing Properties with a Property Bag
Private Sub Class_ReadProperties(PropBag As PropertyBag) With PropBag m_au_id = .ReadProperty("au_id") m_au_fname = .ReadProperty("au_fname") m_au_lname = .ReadProperty("au_lname") m_address = .ReadProperty("address") m_city = .ReadProperty("city") m_state = .ReadProperty("state") m_zip = .ReadProperty("zip") End With End Sub Private Sub Class_WriteProperties(PropBag As PropertyBag) With PropBag .WriteProperty "au_id", m_au_id .WriteProperty "au_fname", m_au_fname .WriteProperty "au_lname", m_au_lname .WriteProperty "address", m_address .WriteProperty "city", m_city .WriteProperty "state", m_state .WriteProperty "zip", m_zip End With End Sub
When the object has persistence behavior, a separate PropertyBag can be used to create a byte array that represents the contents of the property bag. It's the array that can be transferred efficiently across process boundaries. Listing 5 shows how the same author object can be filled from a firehose cursor, changed into a byte array, and transferred to a calling client.
Listing 5: Returning a Byte Array
Public Function GetAuthor(ByVal strSSN As String) As Byte() 'This function returns a byte array 'from a property bag On Error GoTo GetAuthorsErr Dim objRecordset As ADODB.Recordset Dim objAuthor As ByteTrans.Author Dim objBag As PropertyBag 'Run Query Set objRecordset = New ADODB.Recordset objRecordset.Open _ "SELECT * FROM Authors WHERE au_id ='" & strSSN & "'", _ "Provider=SQLOLEDB;Data Source=(local);Initial Catalog=pubs;UID=sa;PWD=;" _ , adOpenForwardOnly, adLockReadOnly 'Create Author object 'NOTE: Use CreateObject because the are in the same DLL! Set objAuthor = CreateObject("ByteTrans.Author") 'Fill object from Recordset With objAuthor .au_id = objRecordset!au_id .au_fname = objRecordset!au_fname .au_lname = objRecordset!au_lname .address = objRecordset!address .city = objRecordset!city .state = objRecordset!state .zip = objRecordset!zip End With objRecordset.Close Set objRecordset = Nothing 'Put object in Property Bag Set objBag = New PropertyBag objBag.WriteProperty "Author", objAuthor 'Return Byte Array GetAuthor = objBag.Contents GetAuthorsExit: Exit Function GetAuthorsErr: Debug.Print Err.Description Resume GetAuthorsExit End Function
After the byte array is transferred to the calling client, a new property bag can be used to reconstitute the original object. This has the effect of allowing you to pass custom objects in your system without the overhead of marshaling. The following code shows how to re-create the same author object in the client process:
'Call Data Layer bytData = objData.GetAuthor(txtSSN) objBag.Contents = bytData 'Re-create Author Object Set objAuthor = objBag.ReadProperty("Author")