A simple yet useful persistence model for VB6
More often than not, I create document based applications. With that I mean
that the user loads some data, works with it for awhile and then, if the changes
were satisfactory, saves the modified over the previous version. But the user
may also choose not to save and then the original data will be unaffected. The
Microsoft Office products Word, Excel and Power Point are all excellent examples
of document based applications.
In such a model, the use of a database as a storage mechanism is actually
counter productive. In no way does the use of a database assist you when writing
a document based application.
I’m going to describe a model for object persistence that has served me well
in several projects during the last years. It is so simple that it is almost
embarrassing to show it. So then, what’s the big deal?
- Simple in this case also means “easy to code”. Typically, for an object to
become persistent you implement ONE interface containing ONE method and then
you fill that method with ONE line of code for each field you want persisted.
- The same line of code that saves the field ALSO loads the field. No risk
that you get your loading code out of sync with the saving code.
- While being simple it is also quite powerful. You actually have total
control over what’s getting saved and loaded.
- Versioning becomes a breeze. Add fields to your document model and old
documents will still load without you taking any action at all. Older programs
will actually still read newer versions of the document! (Unless you prepare
for other actions yourself).
- I haven’t seen anyone else do this.
Also, saving to documents instead of a database has quite a few advantages:
- Versioning (in the sense of not having to maintain several different
versions of a database at different sites).
- Deployment. If something goes wrong during the installation of an
application, it is often the database drivers. Not having to ship them also
reduces the size of the installation file.
- The document can be saved to a removable media or simply mailed directly
from the application. (And also "saved" to the clipboard as well, more on that
later.)
- Speed. Many people use the snail as a metaphor of something being slow. I
myself regard the snail a jet plane compared to a database.
Enough talk. Here’s a sample of code. (Only relevant code lines are shown.)

clsCustomer
Implements iPersistable
Private m_strFirstName As String
Private m_strLastName As String
Private m_Address As clsAddress
Private m_COAddress As clsAddress
Private m_Orders As clsOrders
Private Sub Class_Initialize()
Set m_Address = New clsAddress
Set m_COAddress = New clsAddress
Set m_Orders = New clsOrders
End Sub
Private Sub iPersistable_Persist(ByVal po As iPersist)
With po
.xstr "FIRSTNAME", m_strFirstName
.xstr "LASTNAME", m_strLastName
.xobj "ADDRESS", m_Address
.xobj "COADDRESS", m_COAddress
.xobj "ORDERS", m_Orders
End With
End Sub
clsAddress
Implements iPersistable
Private m_strStreet As String
Private m_strCity As String
Private Sub iPersistable_Persist(ByVal po As iPersist)
With po
.xstr "STREET", m_strStreet
.xstr "CITY", m_strCity
End With
End Sub
clsOrders
Implements iPersistable
Implements iPersistableCol
Private m_col As Collection
Private Sub Class_Initialize()
Set m_col = New Collection
End Sub
Private Sub iPersistable_Persist(ByVal po As iPersist)
po.xcollection "ORDER", Me
End Sub
Private Sub iPersistableCol_LoadItem(ByVal po As iPersist)
Dim objNew As iPersistable
Set objNew = New clsOrder
objNew.Persist po
m_col.add objNew
End Sub
Private Property Get iPersistableCol_NewEnum() As stdole.IUnknown
Set iPersistableCol_NewEnum = m_col.[_NewEnum]
End Property
clsOrder
Implements iPersistable
Private m_strProductID As String
Private m_dateOrder As Date
Private Sub iPersistable_Persist(ByVal po As iPersist)
With po
.xstr "PRODUCTID", m_strProductID
.xdate "DATE", m_dateOrder
End With
End Sub
The code above can be saved to a file like this:
With New clsXMLPersist
.beginSave
.getPersister.xobj "CUSTOMER", m_Customer
.saveXML App.Path & "\customer.xml"
End With
and read back like this:
Set m_Customer = New clsCustomer
With New clsXMLPersist
.openXML App.Path & "\customer.xml"
.getPersister.xobj "CUSTOMER", m_Customer
End With
and this is the file produced:
<?xml version="1.0"?>
<CUSTOMER>
<FIRSTNAME>Donald</FIRSTNAME>
<LASTNAME>Duck</LASTNAME>
<ADDRESS>
<STREET>Applepath 13</STREET>
<CITY>Duckburg</CITY>
</ADDRESS>
<COADDRESS>
<STREET>Äppelstigen 13</STREET>
<CITY>Ankeborg</CITY>
</COADDRESS>
<ORDERS>
<ORDER>
<PRODUCTID>ABC123</PRODUCTID>
<DATE>2004-01-28</DATE>
</ORDER>
<ORDER>
<PRODUCTID>DEF456</PRODUCTID>
<DATE>1934-08-01</DATE>
</ORDER>
<ORDER>
<PRODUCTID>GHI789</PRODUCTID>
<DATE>2000-01-01</DATE>
</ORDER>
</ORDERS>
</CUSTOMER>
Actually, that is the file produced by my clsXMLPersist class.
Another implementation of iPersist is certainly possible. A compressed binary
file for example.
Note that it is easy to do additional actions during the
persistence. The model is quite powerful in that sense. Let’s say that we should
want each order to have a reference to the orders object. I usually try to avoid
that because of the circular references hassle. Anyway, in clsOrders ,
we could have coded like this:
Private Sub iPersistableCol_LoadItem(ByVal po As iPersist)
Dim objNew As iPersistable
Set objNew = New clsOrder
objNew.Persist po
Set objNew.Parent = Me
m_col.add objNew
End Sub
and of course we could store the orders with a key for faster retrieval:
m_col.add objNew, objNew.OrderID
if we have such an ID.
Now let’s say that an order has a reference to a product object instead of a
product ID. Of course we don’t want to store the product object in the document,
duplicating it for each order referencing it. Instead we want to store the
product ID in the persisted data and set the reference while the document is
being read. In this case, the order class could look something like this:
clsOrder, revised
Implements iPersistable
Private m_strProductID As String
Private m_dateOrder As Date
Private Sub iPersistable_Persist(ByVal po As iPersist)
With po
If .isLoading Then
Set m_Product = gProducts(.getStr("PRODUCTID"))
Else
.xstr "PRODUCTID", m_Product.ProductID
End If
.xdate "DATE", m_dateOrder
End With
End Sub
I also this very same mechanism to implement copy-and-paste (clipboard) of
object hierarchies (the whole document or just a part of it). Any object
implementing iPersistable can directly participate in this.
That is pretty much it. The customer / orders example is actually a bad
choice as this is typically NOT a document model, but I chose a concept I think
most developers are comfortable with.
|