DevForce® Classic Tech Tips
Saving and Restoring the Cache 



Saving and Restoring the Cache Level 200
DevForce Express
Aug 7, 2007

The following code persists the DevForce local cache to a local disk file:


C#

private void StoreOffLine() {
  String path = Application.UserAppDataPath + "\\MyLocalData.bin";
  try {
    if (!Directory.Exists(Application.UserAppDataPath)) {
      Directory.CreateDirectory(Application.UserAppDataPath);
    }
    mPersMgr.SaveEntitySet(path);
    MessageBox.Show("Changes saved to local storage");
  }
  catch (Exception ex) {
    MessageBox.Show(ex.Message);
  }
}

   

VB:

Private Sub StoreOffLine()
        Dim path As String = Application.UserAppDataPath & "\MyLocalData.bin"
        Try
            If (Not Directory.Exists(Application.UserAppDataPath)) Then
                Directory.CreateDirectory(Application.UserAppDataPath)
            End If
            mPersMgr.SaveEntitySet(path)
            MessageBox.Show("Changes saved to local storage")
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try
    End Sub

The key to the above method is the call to the PersistenceManager’s SaveEntitySet() method. When you invoke this method, the PersistenceManager’s cache –called an “EntitySet” -- gets stored as a binary but unencrypted file.  In the above code, the static string property Application.UserAppDataPath returns a location similar to the following:

"C:\Documents and Settings\Mary\Application Data\IdeaBlade\GreatAppNo638\1.0.0.0\"

but you may of course store your entity set where you please, calling it what you please.

Restoring the Contents of an Entity Set

You can load a DevForce PersistenceManager cache from the above-created file as follows:

C#: 

    private void LoadOffLine() {
      String path = Application.UserAppDataPath + "\\MyLocalData.bin";
      if (File.Exists(path)) {
        try {
          mPersMgr.RestoreEntitySet(path);
        }
        catch (Exception ex) {
          MessageBox.Show(ex.Message);
          File.Move(path, path + "\\" + DateTime.Now.ToString());
          MessageBox.Show("Local data file not found!");
        }
      }
    }
    #endregion
  }
}

 

VB:

Private Sub LoadOffLine()
        Dim path As String = Application.UserAppDataPath & "\MyLocalData.bin"
        If File.Exists(path) Then
            Try
                mPersMgr.RestoreEntitySet(path)
                LoadData()
            Catch ex As Exception
                MessageBox.Show(ex.Message)
                File.Move(path, path & "\" & DateTime.Now.ToString())
                MessageBox.Show("Local data file not found!")
            End Try
        End If
    End Sub 

Assuming you start with an empty cache, calling RestoreEntitySet() will give you a cache in every respect like the one from which the entity set was originally saved, including entities, query objects, checkpoints, default save options, and default query strategy.

Encrypting the EntitySet

So far so good, but data that’s stored unencrypted had better not be very sensitive:  we all read the regular newspaper stories about sensitive information lost or stolen when an unprotected laptop walked away from its owner.  DevForce provides additional overloads of the SaveEntitySet() and RestoreEntitySet() methods that permit the locally stored data to be encrypted.  You can use these as follows:

C#: 

    private void StoreOffLine() {
      MemoryStream aMemoryStream = new MemoryStream();
      string binaryCacheDataAsString = null;
      string encryptedBinaryCacheDataAsString = null;  

      string folderPath = System.Windows.Forms.Application.UserAppDataPath;
      string filePath = null;
      if (!(IsValidUserAppDataPath(folderPath))) {
        MessageBox.Show("Unable to write data to folder " + folderPath + ".");
        return;
      } else {
        filePath = folderPath + "\\DevForceCache.bin";
        StreamWriter aStreamWriter = new StreamWriter(filePath, false);
        try {
          mPersMgr.SaveEntitySet(aMemoryStream, true);
          binaryCacheDataAsString = Convert.ToBase64String(aMemoryStream.GetBuffer());
          encryptedBinaryCacheDataAsString = IdeaBlade.Util.CryptoFns.SimpleDESEncrypt(binaryCacheDataAsString, mEncryptionKey);
          aStreamWriter.Write(encryptedBinaryCacheDataAsString);
        }
        catch (Exception ex) {
          MessageBox.Show(ex.Message);
        }
        finally {
          aStreamWriter.Flush();
          aStreamWriter.Close();
          string msg = "Local cache contents saved in encrypted form to local storage at: ";
          MessageBox.Show(msg + filePath);
        }
      }
    }
 

VB: 

Private Sub StoreOffLine()
    Dim streamMemory As New MemoryStream
    Dim formatter As New BinaryFormatter
    Dim cipherData As String
    Dim binaryData As String
    Dim path As String = Application.UserAppDataPath + "\\MyLocalData.bin" 

    Try
      If Not Directory.Exists(Application.UserAppDataPath) Then
        Directory.CreateDirectory(Application.UserAppDataPath)
      End If
      ' 1. Open the file
      Dim fs As New StreamWriter(path, False)  

      Try
        ' 2. Get the PM Cache data into the Memory Stram
        mPM.SaveEntitySet(streamMemory, True)  

        ' 3. Encrypt the binary data
        binaryData = Convert.ToBase64String(streamMemory.GetBuffer()) 

        cipherData = DataProtection.Encrypt(binaryData, Nothing) 

        ' 4. Write the data to a file
        fs.Write(cipherData)
      Catch ex As Exception
        Dim s As String = ex.Message
      Finally
        ' 5. Close the file
        fs.Flush()
        fs.Close()
        MessageBox.Show("Local data file has been saved.")
      End Try 

    Catch
      MessageBox.Show("File MyLocalData.bin not found!")
    End Try
  End Sub 

In the above code, we instantiate a MemoryStream and a StreamWriter; call SaveEntitySet() to write the cache contents to the MemoryStream; convert the MemoryStream to an unencrypted string; encrypt the string using any desired encryption method; and order the StreamWriter to write that encrypted string out to the desired file.

Restoring the encrypted entity set follows the process in reverse:

C#: 

private void LoadOffLine() {
  MemoryStream aMemoryStream = null;
  string encryptedBinaryCacheDataAsString = null;
  byte[] binaryCacheData = null;
  string path = System.Windows.Forms.Application.UserAppDataPath + "\\";  

  try {
    //Read the file
    StreamReader aStreamReader = new StreamReader(path + "DevForceCache.bin");
    try {
      //Convert the encrypted data to a string
      encryptedBinaryCacheDataAsString = aStreamReader.ReadToEnd();
      //Decrypt the encrypted string
      binaryCacheData =
        Convert.FromBase64String(IdeaBlade.Util.CryptoFns.SimpleDESDecrypt(
        encryptedBinaryCacheDataAsString, mEncryptionKey));
      //Read the decrypted string into a MemoryStream
      aMemoryStream = new MemoryStream(binaryCacheData);
      //Restore the EntitySet from the decrypted MemoryStream
      mPersMgr.RestoreEntitySet(aMemoryStream, RestoreStrategy.Normal, true);
    }
    catch (Exception ex) {
      MessageBox.Show(ex.Message);
    }
    finally {
      //Close the reader and reload the Employee EntityList
      aStreamReader.Close();
      mEmployees = mPersMgr.GetEntities<Employee>();
      this.mEmployeesBS.DataSource = mEmployees;
      this.mManagersBS.DataSource = mEmployees;
    }
  }
  catch (Exception pException) {
    MessageBox.Show(pException.Message);
  }
} 

 

VB: 

Private Sub LoadOffLine()
    Dim streamMemory As MemoryStream
    Dim formatter As New BinaryFormatter
    Dim cipherData As String
    Dim binaryData As Byte()
    Dim path As String = Application.UserAppDataPath & "\"  

    Try
      ' 1. Open the file
      Dim sr As New StreamReader(path & "MyLocalData.bin")
      Try
        ' 2. Read the binary data, and convert it to a string
        cipherData = sr.ReadToEnd()  

        ' 3. Decrypt the binary data
        binaryData = Convert.FromBase64String(DataProtection.Decrypt(cipherData, Nothing)) 

        ' 4. Rehydrate the dataset
        streamMemory = New MemoryStream(binaryData)       

        mPM.RestoreEntitySet(streamMemory, RestoreStrategy.Normal, True)
      Catch ex As Exception
        Dim s As String = ex.Message
      Finally
        ' 5. Close the reader and rebind the grid
        sr.Close()
        mEmployees = mPM.GetEntities(Of Employee)()
        mEmpSource.DataSource = mEmployees
        ConfigureGrid()
      End Try
    Catch pException As Exception
      MessageBox.Show(pException.Message)
      MessageBox.Show("File MyLocalData.bin not found!")
    End Try
  End Sub 

This time, we create a StreamReader to read the contents of the entity set file and store it into an encrypted string; decrypt the string using a decryption method that mirrors the one we used to encrypt the data; instantiate a memory stream, reading into it the contents of the decrypted string containing the data; and call RestoreEntitySet(), passing it the MemoryStream.

You can control additional aspects of the restoration process by passing a RestoreStrategy to the RestoreEntitySet() call.

C#: 

RestoreStrategy aRestoreStrategy =

  new RestoreStrategy(true, true, MergeStrategy.PreserveChanges);
mPersMgr.RestoreEntitySet(aMemoryStream, aRestoreStrategy, true); 

In the above, we’re using a RestoreStrategy constructor whose details are as follows:

We can choose to overwrite the DefaultSaveOptions and DefaultQueryStrategy in the target cache with those stored in the entity set, or not; and we can determine, by specifying a MergeStrategy, how DevForce will handle duplicates (where an entity being restored, identified by its type and primary key value, matches an entity already in the cache).  The MergeStrategies available are the same as those available when merging data retrieved using an EntityQuery: NotApplicable, OverwriteChanges, PreserveChanges, PreserveChangesUnlessOriginalObsolete, and PreserveChangesUpdateOriginal. For a discussion of these, see the Tech Tip “Query Strategies”, available from the IdeaBlade web site off the Tech Tips page .

EntitySets can be used for many different purposes, including these:

  • to store data for offline work;
  • to store infrequently changing reference data locally so that it need not be retrieved from the database each application session;
  • to make incremental backups of unsaved works

You can save as many EntitySets as you like – just be sure you have a strategy to keep up with what they represent!