Visual Design Softscape  AB
 

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.


Ekeforshus Ekeforshus AB 2004-01-30
Ekeforshus