Skip to main content

Past Blast

Featured Products

Windows Mobile Developer Controls
Windows Mobile Developer Controls
Stay in touch using the DEVBUSS RSS feeds.
 

News

Windows Mobile Developer Controls
Windows Mobile Developer Controls

Forcing Database Changes from ADOCE to Commit

Written by Christopher Tacke  [author's bio]  [read 40491 times]
Edited by Derek

Page 1  Page 2 

With the application running, soft-reset your device. When the OS is back start the application again and this time just tap the third button to show the data in the table. You'll notice that the table is empty. In fact, if you didn't exit the application after creating the database and table, the table doesn't even exist anymore. This same phenomenon occurs with the Recordset object.

This can be a big problem for a deployed application. What if the user works for a few hours adding, modifying and deleting data, then something happens and the device locks up and they have to reset? All of their work is gone and you, at the very least, are getting a tech support call from an understandably upset user.

So what exactly happened that caused our data loss? To understand that we need a basic understanding of how CE uses its databases. When you open a connection to a database through ADOCE, the database is "mounted," meaning that a counter is updated that an application is using the database, and memory is allocated for database modifications. All of the changes are then made in this memory space, not to the actual database itself. If you've done much work with SQL Server, think of it as a transaction.

When the data needs to be committed, the client, in this case your application, must Flush the memory space which causes the system to commit all of the changes. ADOCE has a bug in that a Flush only occurs when the ADOCE object is destroyed. Closing the connection does not force the Flush and therefore when the device is reset all information in volatile memory, including the user's database changes, is lost.

So how do we avoid this? There are two ways. The first, which seems to be the easiest, is to simply set the Connection (or Recordset) equal to Nothing after making your data changes. But that means that every time you need to access a database, you need to call CreateObject, which causes a memory leak in CE. This approach forces the user to reset the device regularly to free the memory eaten by the CreateObject leak.

The second method, which avoids the memory leak and allows you to leave your Recordset or Connection open is to manually flush the database. eVB doesn't have a native method for doing this, but a few simple API calls can achieve it.

The APIs we're interested in are, not surprisingly, CeMountDBVol, CEFlushDBVol and CEUnmountDBVol. Add the following declarations to the General Declarations section of Form1's code page:

Declare Function CeMountDBVol Lib "Coredll" _
(ByVal pGUID As String, _
ByVal lpszVol As String, _
ByVal dwFlags As Long) As Boolean
Declare Function CeFlushDBVol Lib "Coredll" _
(ByVal pGUID As String) As Boolean
Declare Function CeUnmountDBVol Lib "Coredll" _
(ByVal pGUID As String) As Boolean

If you do some searching through eMbedded Visual Tools' Help, you'll see that these declarations are slightly different than the Help definitions. The pGUID parameter in all three is actually a data structure pointer, but eVB doesn't support structures. Fortunately, you don't need to modify the pGUID. CeMountDBVol populates it and you just pass it on to CEFlushDBVol and CEUnmountDBVol, so a String datatype will work fine for holding the pointer.

Now you need a method that wraps these three API calls; call it ADOCECommit. To make it reusable, add the database path as a parameter.

Private Sub ADOCECommit(DatabasePath As String)
Dim s As String
' create memory space for the GUID structure
s = String(8, Chr(0))
' Mount the volume
Call CeMountDBVol(s, DatabasePath, 3)
' Flush the volume - forces changes to commit
Call CeFlushDBVol(s)
' Unmount the volume
Call CeUnmountDBVol(s)
End Sub

The function simply creates enough memory space for the GUID structure, then mounts, flushes and unmounts the database.

The only item left to do is to call ADOCECommit after you insert records. Add a call to ADOCECommit in Command2's click event handler between the data inserts and the Connection close calls. It should now look like this:

Private Sub Command2_Click()
cn.ConnectionString = "\ADOCETest.cdb"
cn.Open
' Insert data
cn.Execute "INSERT INTO TestTable " _
& "VALUES('Fred')"
cn.Execute "INSERT INTO TestTable " _
& "VALUES('Wilma')"
cn.Execute "INSERT INTO TestTable " _
& "VALUES('Barney')"
cn.Execute "INSERT INTO TestTable " _
& "VALUES('Betty')"
' Force the data changes to commit
ADOCECommit "\ADOCETest.cdb"
cn.Close
End Sub

Re-run the earlier test by launching the modified application, inserting data, viewing the data and the resetting the device without exiting the application. Again restart the application and tap the 'Show Data' button. Notice the data is now still there.

You've now seen one of the largest problems in ADOCE and developed a good workaround for it. Using it will not only make your application more solid, it will also prevent some maintenance and support headaches and help keep your application's users happy.

Previous Page