When Isolated Storage Isn’t Enough

Windows Phone From Scratch #38

When you wish to persist state across usages of your application, Isolated Storage allows you to write to the disk and stash away key-value pairs.  For state, this is usually sufficient, but if what you wish to persist is data, especially relational data, then Isolated Storage is a bit limiting.

To meet this need for a more robust data storage scheme, a number of libraries have been developed that work on top of isolated storage.  This posting is the first in a series that will examine these options; beginning with the Sterling Database.

The home page for the Sterling Database states that the goal of Sterling is to be non-intrusive, lightweight, flexible and portable.  The list of features is extensive and there is very good documentation, though it isn’t always obvious (or it wasn’t to me) how to get your first database up and running.

To begin, download Sterling from Codeplex.  There are two ways to install, just the dll’s or the entire source code.  If you use the source code, just include the project into your solution and put a reference in your main project to the Sterling project.

You can also install Sterling on a per-project basis using NuGet.  To do so, create a new Windows Phone 7 project and right click on the references and choose Add Library Package Reference. Click on On-Line and then in the search box write Sterling, as shown in the figure

NuGetSterling

Remember that NuGet installs on a per-project basis.

Let’s create the simplest possible UI to demonstrate storage and retrieval of information. Here’s the complete Xaml:

<phone:PhoneApplicationPage
<!-- headers elided -->
>

   <Grid
      x:Name="LayoutRoot"
      Background="Transparent">
      <Grid.RowDefinitions>
         <RowDefinition
            Height="Auto" />
         <RowDefinition
            Height="*" />
      </Grid.RowDefinitions>

      <StackPanel
         x:Name="TitlePanel"
         Grid.Row="0"
         Margin="12,17,0,28">
         <TextBlock
            x:Name="ApplicationTitle"
            Text="Database Storage"
            Style="{StaticResource PhoneTextNormalStyle}" />
         <TextBlock
            x:Name="PageTitle"
            Text="Sterling"
            Margin="9,-7,0,0"
            Style="{StaticResource PhoneTextTitle1Style}" />
      </StackPanel>

      <Grid
         x:Name="ContentPanel"
         Grid.Row="1"
         Margin="12,0,12,0">

         <StackPanel>
            <TextBlock
               Name="Message"
               Text="Hello"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
            <Button
               Name="Test"
               Content="Test"
               Height="Auto"
               Width="100" />
         </StackPanel>

      </Grid>
   </Grid>
</phone:PhoneApplicationPage>

Create three member variables at the top of your code-behidn page. The first two will be for registration of the Database engine and an instance of the database, and the third will be an instance of the Customer class that we’ll create,

private SterlingEngine _engine;
private ISterlingDatabaseInstance _databaseInstance;
private Customer cust;

The customer class will serve as the data we want to store. Its only requirement is that each instance of Customer have a unique ID,

public class Customer
{
   public int CustomerID { get; set; }
   public string CustomerName { get; set; }
}

You also need a class that derives from BaseDatabaseInstance. We’ll call our class TestDB.  The requirement is that you override the Name property and the _RegisterTables method.  The latter is where you will define each table for your database in terms of each class,

public class TestDB : BaseDatabaseInstance
{
   public override string Name
   {
      get
      {
         return "TestDB";
      }
   }

   protected override List<ITableDefinition>
                         _RegisterTables( )
   {
      return new List<ITableDefinition>( )
      {
         CreateTableDefinition<Customer, int>(
                   k => k.CustomerID)
      };
   }
}

CreateTableDefinition takes the name of the class and the type of the key. You pass in a lambda expression that resolves to the unique key.  It is also possible to define indices at this point, but to keep this first example simple, we’ll not implement anything unless it is absolutely required.

In the PageLoaded event we’ll register the click event, and call three methods. The first will start up the Sterling database, the second will create an instance of Customer and the third will store that instance in the database,

 public MainPage( )
 {
    InitializeComponent( );
    Loaded +=
       new RoutedEventHandler( MainPage_Loaded );
 }

 private void MainPage_Loaded(
    object sender, RoutedEventArgs e )
 {
    Test.Click +=
       new System.Windows.RoutedEventHandler(
          Test_Click );
    StartSterling( );
    CreateCustomer( );
    SaveCustomer( );
 }

StartSterling is boilerplate code for starting up the database,

private void StartSterling( )
{
   _engine = new SterlingEngine( );
   _engine.Activate( );
   _databaseInstance =
      _engine
      .SterlingDatabase
      .RegisterDatabase<TestDB>( );
}

Create Customer instantiates the Customer class we created earlier and assigns the instance to the member variable. Save customer calls _databaseInstance.Save passing in the customer, and we follow that call with a call to Flush() to flush the queue to storage.

private void CreateCustomer( )
{
   cust = new Customer( )
   {
      CustomerID = 1,
      CustomerName = "John Doe"
   };
}

private void SaveCustomer( )
{
   var key =
      _databaseInstance.Save( cust );
   _databaseInstance.Flush( );
}

Notice that all you do to save the data is call Save and Sterling takes care of the rest.  You do not interact with isolated storage directly at all.

We’ll wire up the button to call Load on the database instance, passing in the customer ID (in this case hard-wired) and then we’ll retrieve the Customer name from the loaded record and display that in the TextBlock,

private void Test_Click(
   object sender,
   RoutedEventArgs e )
{
   var customer =
      _databaseInstance
      .Load<Customer>( 1 );
   var name = customer.CustomerName;
   Message.Text = name;
}

The net effect is that when  you click the button the customer is retrieved from the database and the name is displayed.

Special thanks to Michael Washington and Jeremy Likness for their help in getting me started with Sterling.

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in Patterns & Skills and tagged , , , , . Bookmark the permalink.

5 Responses to When Isolated Storage Isn’t Enough

Comments are closed.