Persisting Data in Your Windows Mobile Device

Persisting Data in Your Windows Mobile Device

n a previous article on Windows Mobile, I showed how you can develop a simple Windows Mobile 6 application and then deploy it by creating a CAB file. I also showed you the various connectivity options available in Windows Mobile that allow you to connect to the outside world. At some point in time, you’ll also need the ability to store data onto the device persistently?things like saving preferences, user-specific data, configuration information, and so on. You have a number of methods at your disposal to persist data. This article will explain your options and help you choose the right one for your device.

The most common way to persist data to storage is to use a file. Files allow you to write text or binary content to them and then easily read said content. To manipulate files, you can use the File class, which provides static methods for dealing with files. The File class is defined within the System.IO namespace, so before using the File class, you need to import the System.IO namespace:

using System.IO;

The following example shows how you can open a file for writing text content using the AppendText() method of the File class:

            string Path = @"textfile.txt";            string str1 = "This is a string.";            string str2 = "This is another string.";            StreamWriter sw = File.AppendText(Path);            sw.Write(str1);            sw.WriteLine(str2);            sw.Close();

The AppendText() method returns a StreamWriter object (also defined in the System.IO namespace), which allows you to write characters to a stream. The StreamWriter class has two methods you can use to write text into the file: Write() or WriteLine().

To open a text file for reading, use the File class’ OpenText() method:

            StreamReader sr = File.OpenText(Path);            string strRead;            while ((strRead = sr.ReadLine()) != null)            {                MessageBox.Show(strRead);            }

The OpenText() method returns a StreamReader object (also defined in the System.IO namespace), which allows you to read characters from a stream. In the above example, you read each individual line from the file and print them out using the MessageBox class until there are no more lines to be read.

To deal with binary contents, you should use the File class’ OpenRead() method to open a file for reading and the OpenWrite() method to open it for writing. Both methods return a FileStream object (also defined in the System.IO namespace). You can then use the BinaryReader class to read binary data from the FileStream object, and the BinaryWriter class to write binary data to a FileStream object. Listing 1 shows how to copy an image (a binary file) byte-by-byte into another file, essentially making a copy of the file.

While writing to and reading from files is a straightforward affair, the downside is that there is no sophisticated mechanism to help you manage the content of the file. For example, suppose you want to replace part of the file with some data. You’d need to seek to the exact location of the data before you could replace it. And in most cases, this simple task involves reading from the original file, filtering the necessary data, and then rewriting the data back to the original file. Hence, you should use files for storing simple data?comments, error logs, and so on.

For more structured data, using a database is more appropriate.

SQL Server Compact 3.5 Database
If you need to store structured data (for example, suppose you have hundreds of customers’ information that you need to store on your Windows Mobile device), a database solution is more appropriate for you. For the Windows Mobile platform, your database of choice would be SQL Server Compact 3.5. This is a free, embedded database engine that lets developers build robust Windows desktop and mobile applications that run on all Windows platforms including Windows XP, Vista, Pocket PC, and Smartphone.

Using a SQL Compact Edition Database
When you install Visual Studio 2008, a sample SQL Server Compact database named Northwind.sdf is also installed automatically in the C:Program FilesMicrosoft SQL Server Compact Editionv3.5Samples folder. This sample database is useful for developers just getting started with SQL Server Compact 3.5.

To use the SQL Server Compact 3.5 Northwind.sdf database, add an existing item to your project and navigate to the folder containing the sample database. Once it is added, a wizard will appear, asking you to choose the table that you want to use (see Figure 1). Essentially, this creates a typed DataSet for you.

Figure 1. The Database Wizard: Choosing the table(s) you want to use in your project.

Once you’ve selected the table(s) you want to use, a typed Dataset (NorthwindDataSet, in this case) is created for you. You can use the code shown in Listing 2 to load the Employees table into a DataSet.

The SqlCeConnection class represents a connection to a SQL Server Compact database and the SqlCeCommand class represents a SQL statement to execute against a data source. The SqlCeDataAdapter class represents a set of commands and a database connection that can be used to fill a DataSet object. If you are familiar with ADO.NET, then the above few classes should not be too alien to you. Once you’ve loaded the table(s) into a DataSet, you can bind the DataSet object to a DataGrid control, retrieve the rows within the tables in the DataSet, or modify the records directly in the DataSet.

If you do not wish to use the typed DataSet that Visual Studio 2008 created for you, you can also use the generic DataSet object, like this:

conn.Open();            SqlCeDataAdapter adapter = new SqlCeDataAdapter(cmd);            DataSet ds = new DataSet();            adapter.Fill(ds, "Employees");            foreach (DataRow r in ds.Tables["Employees"].Rows)                textBox1.Text += r[1].ToString() + Environment.NewLine;            conn.Close();

To persist the content of the DataSet object to storage, use the DataSet class’ WriteXml() method. This saves the DataSet as an XML file:


To load the DataSet from an XML file, use the ReadXml() method:


If you simply want to print out the values of some fields in the table, it might be easier to use the SqlCeDataReader class, which provides a forward-only stream of data from a data source:

            conn.Open();            SqlCeDataReader reader;            SqlCeCommand cmd = new SqlCeCommand(                "SELECT * FROM Employees", conn);            reader = cmd.ExecuteReader();            //---read all the Employee ID, First Name, and Last Name---            while (reader.Read())            {                comboBox1.Items.Add(                    reader["Employee ID"].ToString() + " - " +                    reader["First Name"].ToString() + ", " +                    reader["Last Name"].ToString());            }            conn.Close();

The above code uses the SqlCeCommand class’ ExecuteReader() method to return a SqlCeReader object. The SqlCeReader object is then used to display the EmployeeID, First Name, and Last Name fields of the Employees table in a ComboBox control. The SqlCeDataReader class’ Read() method advances the reader to the next row of data and returns true as long as there are more data to read.

To update a record, use the SqlCeCommand class’ ExecuteNonQuery() method to execute an SQL statement, like this:

            conn.Open();            string sql = "UPDATE Employees " +               "SET [First Name]='Wei-Meng' ,[Last Name]='Lee' " +               "WHERE [Employee ID]=1";            cmd.CommandText = sql;            cmd.ExecuteNonQuery();            MessageBox.Show("Name modified!");            conn.Close();

Creating a SQL Compact Edition Database Programmatically
The previous section explains how to use a SQL Server Compact database that is pre-built. But occasionally, you may need to create one from scratch. programmatically. You can do so using the SqlCeEngine and the SqlCeCommand classes, as shown in Listing 3.

Listing 3 creates a new SQL Server Compact database using the SqlCeEngine class’ CreateDatabase() method. To create individual tables within the database, use the SqlCeCommand class. The above code creates two tables: Stores and Orders. The Stores table contains two fields: storeID (primary key) and storeName. The Orders table contains two fields: storeID and title_id.

The following code adds a record into the Stores table:

                string storeID = "1";                string storeName = "Great BookStore";                cmd.CommandText =                    "INSERT INTO Stores (storeID, " +                    "storeName) VALUES (@storeID, " +                    "@storeName)";                cmd.Parameters.Add("@storeID", storeID);                cmd.Parameters.Add("@storeName", storeName);                cmd.ExecuteNonQuery();

You are advised to use a Parameters object when formulating SQL statements. If not, security risks like SQL-injection may occur (especially if the inputs are from end-users).

See my previous article about ASP.NET security for a discussion on SQL injection.

Design Tool
Besides programmatically creating the SQL Server Compact database, you can also use Visual Studio 2008 to create one visually.

In your project, add a new Database File (see Figure 2).

Figure 2. Create a Database Visually: Adding a database file to a project.
Figure 3. Create a Database Visually: Creating a new table.

After you’ve added the database file, the Data Source Configuration Wizard will appear. As there is no table to select (the database is empty at the moment), simply click Finish.

In the Server Explorer, you’ll notice that the database just added is now listed under the Data Connections item (see Figure 3). Right-click on the Tables item and select Create Table.

The New Table window will appear (see Figure 4). You can name your new table as well as define the columns in this window. Once you are done, click OK.

Figure 4. Create a Database Visually: Defining a new table in the New Table window.
Figure 5. Create a Database Visually: Displaying the content of a table.

To populate the table with some records, right-click on the newly created table name in Server Explorer and select Show Table Data (see Figure 5). You can now populate your table with some rows.

So far, you’ve learned that for simple storage of data, using files is the best solution while for complex structured data, a database is more appropriate. However, there are times in which both solutions are not really suitable. For example, suppose you need to store some configuration information about your application (such as users’ preferences, network credentials, and so on). In such cases, using files is clumsy because you need to manually keep track of all the information you are storing (data length, fields, etc.). But using a database is overkill because the data you are storing is not repeating. A good solution to this problem is to use XML. An XML document allows you to store the information in a context-rich format. At the same time, you do not need to worry about the nitty-gritty details of retrieving and storing the data?such things can be taken care of by .NET Compact Framework’s XML classes.

Listing 4 shows how you can create an XML file named config.xml and create a root element named with two sub-elements: and .

When executed, the content of the config.xml file would look like this:

   5/12/08 10:32:21 PM   sushi

To load the XML document into memory, simply use the XmlDocument class’ Load() method:

            XmlDocument xml = new XmlDocument();            xml.Load(configFile);

To retrieve the contents of the and the elements, use the XmlDocument class’s SelectSingleNode() method by passing in an XPath expression for each method, like this:

//---XPath expression 1---            XmlNode node =  xml.SelectSingleNode("Configuration/LastAccess");            MessageBox.Show(node.InnerText);            //---XPath expression 2---            node = xml.SelectSingleNode("//LastSearchString");            MessageBox.Show(node.InnerText);

Note that the above methods are useful if your XML document is relatively simple. If you have a large and complex XML document, you’d spend a lot of time writing XPath expressions to look for the relevant elements to retrieve.

An improved method is to use XML serialization. config.xml could be appropriately represented using a class. For example, you can define the Configuration class to look like this:

public class Configuration    {        public DateTime LastAccess { get; set; }        public string LastSearchString { get; set; }    }

To store the last access date and the last search string, create an instance of the Configuration class:

            Configuration config = new Configuration()            {                LastAccess = DateTime.Now,                LastSearchString = "sushi"            };

When you are ready to store the last access date and last search string to an XML document, serialize the object using XML serialization:

            string configFile = @"config.xml";            StreamWriter sw = new StreamWriter(configFile);            try            {                XmlSerializer s = new XmlSerializer(typeof(Configuration));                s.Serialize(sw, config);            }            catch (Exception ex)            {                Console.WriteLine(ex.ToString());            }            finally            {                sw.Close();            }

The above code uses the XmlSerializer class to serialize an object (a Configuration object) and then writes the output to an XML file using the StreamWriter class. The config.xml file will then look like this:

   2008-05-12T23:28:13-07:00   sushi

To read the XML document back into a Configuration object, you will reverse the process, that is, you will now de-serialize the object. The following code uses the XmlReader class to read the config.xml file and then uses the XmlSerializer class to de-serialize the XML content read by the XmlReader object:

Figure 6. Reading config.xml: The content read from the de-serialization of the XML document.
config = null;            XmlReader xr = XmlReader.Create(configFile);            try            {                XmlSerializer s = new XmlSerializer(typeof(Configuration));                config = (Configuration)s.Deserialize(xr);            }            catch (Exception ex)            {                Console.WriteLine(ex.ToString());            }            finally            {                xr.Close();            }            MessageBox.Show(config.LastAccess.ToString());            MessageBox.Show(config.LastSearchString);

The above code will print out the message box shown in Figure 6.

Three Methods, Lots of Flexiblity
As a quick recap, here are your options for persisting data to your Windows Mobile devices:

  • File: This method is useful for storing simple data such as error logs, comments, etc.
  • Database: This method is useful for storing large amount of structured data, such as customer records, products list, etc.
  • XML Documents: This method is useful for storing data that needs to be read and written frequently, such as configuration information, user credentials, etc. In particular, XML serialization is extremely useful for storing configuration information.


Share the Post: