Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Windows Phone 8 Succinctly: Data Access—Storage

$
0
0

Local Storage

The Internet plays an important role in mobile applications. Most Windows Phone applications available in the Store make use of the network connection offered by every device. However, relying only on the network connection can be a mistake; users can find themselves in situations where no connection is available. In addition, data plans are often limited, so the fewer network operations we do, the better the user experience is.

Windows Phone offers a special way to store local data called isolated storage. It works like a regular file system, so you can create folders and files as on a computer hard drive. The difference is that the storage is isolated—only your applications can use it. No other applications can access your storage, and users are not able to see it when they connect their phone to the computer. Moreover, as a security measure, the isolated storage is the only storage that the application can use. You’re not allowed to access the operating system folders or write data in the application’s folder.

Local storage is one of the features which offers duplicated APIs—the old Silverlight ones based on the IsolatedStorageFile class and the new Windows Runtime ones based on the LocalFolder class. As mentioned in the beginning of the series, we’re going to focus on the Windows Runtime APIs.

Working With Folders

The base class that identifies a folder in the local storage is called StorageFolder. Even the root of the storage (which can be accessed using the ApplicationData.Current.LocalStorage class that is part of the Windows.Storage namespace) is a StorageFolder object.

This class exposes different asynchronous methods to interact with the current folder, such as:

  • CreateFolderAsync() to create a new folder in the current path.
  • GetFolderAsync() to get a reference to a subfolder of the current path.
  • GetFoldersAsync() to get the list of folders available in the current path.
  • DeleteAsync() to delete the current folder.
  • RenameAsync() to rename a folder.

In the following sample, you can see how to create a folder in the local storage’s root:

Unfortunately, the APIs don’t have a method to check if a folder already exists. The simplest solution is to try to open the folder using the GetFolderAsync() method and intercept the FileNotFoundException error that is raised if the folder doesn’t exist, as shown in the following sample:

Working With Files

Files, instead, are identified by the StorageFile class, which similarly offers methods to interact with files:

  • DeleteAsync() to delete a file.
  • RenameAsync() to rename a file.
  • CopyAsync() to copy a file from one location to another.
  • MoveAsync() to move a file from one location to another.

The starting point to manipulate a file is the StorageFolder class we’ve previously discussed, since it offers methods to open an existing file (GetFileAsync()) or to create a new one in the current folder (CreateFileAsync()).

Let’s examine the two most common operations: writing content to a file and reading content from a file.

How to Create a File

As already mentioned, the first step to create a file is to use the CreateFile() method on a StorageFolder object. The following sample shows how to create a new file called file.txt in the local storage’s root:

You can also pass the optional parameter CreationCollisionOption to the method to define the behavior to use in case a file with the same name already exists. In the previous sample, the ReplaceExisting value is used to overwrite the existing file.

Now that you have a file reference thanks to the StorageFile object, you are able to work with it using the OpenAsync() method. This method returns the file stream, which you can use to write and read content.

The following sample shows how to write text inside the file:

The key is the DataWriter class, which is a Windows Runtime class that can be used to easily write data to a file. We simply have to create a new DataWriter object, passing as a parameter the output stream of the file we get using the GetOuputStreamAt() method on the stream returned by the OpenAsync() method.

The DataWriter class offers many methods to write different data types, like WriteDouble() for decimal numbers, WriteDateTime() for dates, and WriteBytes() for binary data. In the sample we write text using the WriteString() method, and then we call the StoreAsync() and FlushAsync() methods to finalize the writing operation.

Note: The using statement can be used with classes that support the IDisposable interface. They are typically objects that lock a resource until the operation is finished, like in the previous sample. Until the writing operation is finished, no other methods can access the file. With the using statement, we make sure that the lock is released when the operation is completed.

How to Read a File

The operation to read a file is not very different from the writing one. In this case, we also need to get the file stream using the OpenFile() method. The difference is that, instead of using the DataWriter class, we’re going to use the DataReader class, which does the opposite operation. Look at the following sample code:

In this case, instead of the CreateFileAsync() method, we use the GetFileAsync() method, which can be used to get a reference to an already existing file. Then, we start the reading procedure using the DataReader class, this time using the input stream that we get using the GetInputStreamAt() method.

Like the DataWriter class, DataReader also offers many methods to read different data types, like ReadDouble(), ReadDateTime(), and ReadBytes(). In this case, we read the text we’ve previously written by using the ReadString() method, which requires the size of the file as its parameter.

A Special Folder: InstalledLocation

The local storage is the only storage we can use to write our application’s data, but in some cases, we may need to include in our project some existing files that need to be processed by the application.

The Windows Runtime offers an API to provide access to the folder where the application is installed and where all the files that are part of your Visual Studio project are copied. It’s called Package.Current.InstalledLocation, and it’s part of the Windows.ApplicationModel namespace.

The InstalledLocation’s type is StorageFolder, like the folders in local storage, so you can use the same methods to work with files and folders. Keep in mind that you won’t be able to write data, but only read it.

In the following sample, we copy a file from the application’s folder to the local storage so that we gain write access.

Note: During development you may notice that you’ll be able to execute write operations in the application’s folder. Don’t count on it—during the certification process, the app is locked, so when the app is distributed on the Windows Phone Store, the write access is revoked and you’ll start getting exceptions.

Manage Settings

One common scenario in mobile development is the need to store settings. Many applications offer a Settings page where users can customize different options.

To allow developers to quickly accomplish this task, the SDK includes a class called IsolatedStorageSettings, which offers a dictionary called ApplicationSettings that you can use to store settings.

Note: The IsolatedStorageSettings class is part of the old storage APIs; the Windows Runtime offers a new API to manage settings but, unfortunately, it isn’t available in Windows Phone.

Using the ApplicationSettings property is very simple: its type is Dictionary<string, object> and it can be used to store any object.

In the following sample, you can see two event handlers: the first one saves an object in the settings, while the second one retrieves it.

The only thing to highlight is the Save() method, which you need to call every time you want to persist the changes you’ve made. Except for this, it works like a regular Dictionary collection.

Note: Under the hood, settings are stored in an XML file. The API automatically takes care of serializing and deserializing the object you save. We’ll talk more about serialization later in this article.

Debugging the Local Storage

A common requirement for a developer working with local storage is the ability to see which files and folders are actually stored. Since the storage is isolated, developers can’t simply connect the phone to a computer and explore it.

The best way to view an application’s local storage is by using a third-party tool available on CodePlex called Windows Phone Power Tools, which offers a visual interface for exploring an application’s local storage.

The tool is easy to use. After you’ve installed it, you’ll be able to connect to a device or to one of the available emulators. Then, in the Isolated Storage section, you’ll see a list of all the applications that have been side-loaded from Visual Studio. Each one will be identified by its application ID (which is a GUID). Like a regular file explorer, you can expand the tree structure and analyze the storage’s content. You’ll be able to save files from the device to your PC, copy files from your PC to the application storage, and even delete items.

Local Storage of a Windows Phone Application

Storing Techniques

In the previous section, we discussed the basic APIs available to store files and folders in your application. In this section, we’ll go deeper to see the best ways to store your application’s data, so that it can be maintained across different applications.

Serialization and Deserialization

Serialization is the simplest way to store an application’s data in the local storage. It’s the process that converts complex objects into plain text so that they can be stored in a text file, using XML or JSON as output. Deserialization is the opposite process; the plain text is converted back into objects so that they can be used by the application.

In a Windows Phone application that uses these techniques, serialization is typically applied every time the application’s data is changed (when a new item is added, edited, or removed) to minimize the risk of losing data if something happens, like an unexpected crash or a suspension. Deserialization, instead, is usually applied when the application starts for the first time.

Serialization is very simple to use, but its usage should be limited to applications that work with small amounts of data, since everything is kept in memory during the execution. Moreover, it best suits scenarios where the data to track is simple. If you have to deal with many relationships, databases are probably a better solution (we’ll talk more about this later in the article).

In the following samples, we’re going to use the same Person class we used earlier in this series.

We assume that you will have a collection of Person objects, which represents your local data:

Serialization

To serialize our application’s data we’re going to use the local storage APIs we learned about in the previous section. We’ll use the CreateFile() method again, as shown in the following sample:

The DataContractSerializer class (which is part of the System.Runtime.Serialization namespace) takes care of managing the serialization process. When we create a new instance, we need to specify which data type we’re going to serialize (in the previous sample, it’s List<Person>). Next, we create a new file in the local storage and get the stream needed to write the data. The serialization operation is made by calling the WriteObject() method of the DataContractSerializer class, which requires as parameters the stream location in which to write the data and the object to serialize. In this example, it’s the collection of Person objects we’ve previously defined.

If you take a look at the storage content using the Windows Phone Power Tools, you’ll find a people.xml file, which contains an XML representation of your data:

Tip: The DataContractSerializer class uses XML as its output format. If you want to use JSON instead, you’ll have to use the DataContractJsonSerializer class, which works in the same way.

Deserialization

The deserialization process is very similar and involves, again, the storage APIs to read the file’s content and the DataContractSerializer class. The following sample shows how to deserialize the data we serialized in the previous section:

The only differences are:

  • We get a stream to read by using the AsStreamForRead() method.
  • We use the ReadObject() method of the DataContractSerializer class to deserialize the file’s content, which takes the file stream as its input parameter. It’s important to note that the method always returns a generic object, so you’ll always have to cast it to your real data type (in the sample, we cast it as List<Person>).

Using Databases: SQL CE

When you develop complex applications, you probably have to deal with complex data. Databases are a good solution to manage this scenario because they support relationships, and because the entire dataset is not kept in memory, only the needed items are.

SQL CE is the database solution that was introduced in Windows Phone 7.5. It’s a stand-alone database, which means that data is stored in a single file in the storage without needing a DBMS to manage all the operations.

Windows Phone uses SQL CE 3.5 (the latest release at this time is 4.0, but it is not supported) and doesn’t support SQL query execution. Every operation is made using LINQ to SQL, which is one of the first of Microsoft’s ORM solutions.

Note: ORM (Object-Relation Mapping) solutions are libraries that are able to automatically translate object operations (insert, edit, remove) into database operations. This way, you can keep working on your project using an object-oriented approach. ORM will take care of writing the required SQL queries to store your data in the database.

The approach used by SQL CE on Windows Phone is called code first. The database is created the first time the data is needed, according to the entities definition that you’re going to store in tables. Another solution is to include an already existing SQL CE file in your Visual Studio project. In this case, you’ll only be able to work with it in read-only mode.

How to Define the Database

The first step is to create the entities that you’ll need to store in your database. Each entity will be mapped to a specific table.

Entity definition is made using attributes, which are part of the System.Data.Linq.Mapping namespace. Each property is decorated with an attribute, which will be used to translate it into a column. In the following sample we adapt the familiar Person class to be stored in a table:

The entire entity is marked with the Table attribute, while every property is marked with the Column attribute. Attributes can be customized with some properties, like:

  • IsPrimaryKey to apply to columns that are part of the primary key.
  • IsDbGenerated in case the column’s value needs to be automatically generated every time a new row is inserted (for example, an automatically incremented number).
  • Name if you want to assign to the column a different name than the property.
  • DbType to customize the column’s type. By default, the column’s type is automatically set by the property’s type.

Working With the Database: The DataContext

DataContext is a special class that acts as an intermediary between the database and your application. It exposes all the methods needed to perform the most common operations, like insert, update, and delete.

The DataContext class contains the connection string’s definition (which is the path where the database is stored) and all the tables that are included in the database. In the following sample, you can see a DataContext definition that includes the Person table we’ve previously defined:

A separate class of your project inherits from the DataContext class. It will force you to implement a public constructor that supports a connection string as its input parameter. There are two connection string types, based on the following prefixes:

  • isostore:/ means that the file is stored in the local storage. In the previous sample, the database’s file name is Persons.sdf and it’s stored in the storage’s root.
  • appdata:/ means that the file is stored in the Visual Studio project instead. In this case, you’re forced to set the File Mode attribute to Read Only.

Eventually, you can also encrypt the database by adding a Password attribute to the connection string:

Creating the Database

As soon as the data is needed, you’ll need to create the database if it doesn’t exist yet. For this purpose, the DataContext class exposes two methods:

  • DatabaseExists() returns whether the database already exists.
  • CreateDatabase() effectively creates the database in the storage.

In the following sample, you can see a typical database initialization that is executed every time the application starts:

Working With the Data

All the operations are made using the Table<T> object that we’ve declared in the DataContext definition. It supports standard LINQ operations, so you can query the data using methods like Where(), FirstOrDefault(), Select(), and OrderBy().

In the following sample, you can see how we retrieve all the Person objects in the table whose name is Matteo:

The returned result can be used not only for display purposes, but also for editing. To update the item in the database, you can change the values of the returned object by calling the SubmitChanges() method exposed by the DataContext class.

To add new items to the table, the Table<T> class offers two methods: InsertOnSubmit() and InsertAllOnSubmit(). The first method can be used to insert a single object, while the second one adds multiple items in one operation (in fact, it accepts a collection as a parameter).

Please note again the SubmitChanges() method: it’s important to call it every time you modify the table (by adding a new item or editing or deleting an already existing one), otherwise changes won’t be saved.

In a similar way, you can delete items by using the DeleteOnSubmit() and DeleteAllOnSubmit() methods. In the following sample, we delete all persons with the name Matteo:

Relationships

In the previous sections, we’ve talked about data that is stored in a single table. Now it’s time to introduce relationships, which are a way to connect two or more tables. As an example, we’ll add a new Order entity to our database, which we’ll use to save the orders made by users stored in the Person table.

With LINQ to SQL we’ll be able to:

  • Add a Person property to the Order entity that will store a reference to the user who made the order.
  • Add an Orders collection to the Person entity that will contain all the orders made by the user.

This is accomplished by using a foreign key, which is a property declared in the Order entity that will hold the primary key value of the user who made the order. 

Here is how the Order class looks:

There are two key properties in the class definition:

  • PersonId is the foreign key, which simply holds the person’s ID.
  • Person is a real Person object that, thanks to the Association attribute, is able to hold a reference to the user who made the order. The property’s setter contains some logic to manage whether you’re adding a new value or removing an already existing one.

Of course, we have to also change the Person class definition in order to manage the relationships:

Also in this class, we’ve defined a new property called Orders, whose type is EntitySet<T>, where T is the type of the other table involved in the relationship. Thanks to the Association attribute, we are be able to access all the orders made by a user simply by querying the Orders collection.

In the following samples, you can see two common operations in which a relationship is involved: creation and selection.

Since Person is a property of the Order class, it’s enough to create a new order and set the object that represents the user who made the order as a value of the Person property.

In the same way, when we get an order we are able to get the user’s details simply by querying the Person property. In the previous sample, we display the name of the user who made the order.

Updating the Schema

A common scenario when you’re planning to release an application update is that you’ve changed the database schema by adding a new table or new column, for example.

SQL CE in Windows Phone offers a specific class to satisfy this requirement, called DatabaseSchemaUpdater, which offers some methods to update an already existing database’s schema.

Note: The DatabaseSchemaUpdater’s purpose is just to update the schema of an already existing database. You still need to update your entities and DataContext definition to reflect the new changes.

The key property offered by the DatabaseSchemaUpdater class is DatabaseSchemaVersion, which is used to track the current schema’s version. It’s important to properly set it every time we apply an update because we’re going to use it when the database is created or updated to recognize whether we’re using the latest version.

After you’ve modified your entities or the DataContext definition in your project, you can use the following methods:

  • AddTable<T>() if you’ve added a new table (of type T).
  • AddColumn<T>() if you’ve added a new column to a table (of type T).
  • AddAssociation<T>() if you’ve added a new relationship to a table (of type T).

The following sample code is executed when the application starts and needs to take care of the schema update process:

We’re assuming that the current database’s schema version is 2. In case the database doesn’t exist, we simply create it and, using the DatabaseSchemaUpdater class, we update the DatabaseSchemaVersion property. This way, the next time the data will be needed, the update operation won’t be executed since we’re already working with the latest version.

Instead, if the database already exists, we check the version number. If it’s an older version, we update the current schema. In the previous sample, we’ve added a new column to the Person table, called BirthDate (which is the parameter requested by the AddColumn<T>() method). Also in this case we need to remember to properly set the DatabaseSchemaVersion property to avoid further executions of the update operation.

In both cases, we need to apply the described changes by calling the Execute() method.

SQL Server Compact Toolbox: An Easier Way to Work With SQL CE

Erik Ej, a Microsoft MVP, has developed a powerful Visual Studio tool called SQL Server Compact Toolbox that can be very helpful for dealing with SQL CE and Windows Phone applications.

Two versions of the tool are available:

  • As an extension that’s integrated into commercial versions of Visual Studio.
  • As a stand-alone tool for Visual Studio Express since it does not support extensions.

The following are some of the features supported by the tool:

  • Automatically create entities and a DataContext class starting from an already existing SQL CE database.
  • The generated DataContext is able to copy a database from your Visual Studio project to your application’s local storage. This way, you can start with a prepopulated database and, at the same time, have write access.
  • The generated DataContext supports logging in the Visual Studio Output Window so you can see the SQL queries generated by LINQ to SQL.

Using Databases: SQLite

SQLite, from a conceptual point of view, is a similar solution to SQL CE: it’s a stand-alone database solution, where data is stored in a single file without a DBMS requirement.

The pros of using SQLite are: 

  • It offers better performance than SQL CE, especially with large amounts of data.
  • It is open source and cross-platform; you’ll find a SQLite implementation for Windows 8, Android, iOS, web apps, etc.

SQLite support has been introduced only in Windows Phone 8 due to the new native code support feature (since the SQLite engine is written in native code), and it’s available as a Visual Studio extension that you can download from the Visual Studio website.

After you’ve installed it, you’ll find the SQLite for Windows Phone runtime available in the Add reference window, in the Windows Phone Extension section. Be careful; this runtime is just the SQLite engine, which is written in native code. If you need to use a SQLite database in a C# application, you’ll need a third-party library that is able to execute the appropriate native calls for you.

In actuality, there are two available SQLite libraries: sqlite-net and SQLite Wrapper for Windows Phone. Unfortunately, neither of them is as powerful and flexible as the LINQ to SQL library that is available for SQL CE.

Let’s take a brief look at them. We won’t dig too deeply. Since they’re in constant development, things can change very quickly.

Sqlite-net

Sqlite-net is a third-party library. The original version for Windows Store apps is developed by Frank A. Krueger, while the Windows Phone 8 port is developed by Peter Huene.

The Windows Phone version is available on GitHub. Its configuration procedure is a bit tricky and changes from time to time, so be sure to follow the directions provided by the developer on the project’s home page.

Sqlite-net offers a LINQ approach to use the database that is similar to the code-first one offered by LINQ to SQL with SQL CE.

For example, in sqlite-net, tables are mapped with your project’s entities. The difference is that, this time, attributes are not required since every property will be automatically translated into a column. Attributes are needed only if you need to customize the conversion process, as in the following sample:

Surname doesn’t have any attribute, so it will be automatically converted into a varchar column. Instead, we set Id as a primary key with an auto increment value, while we specify that Name can have a maximum length of 50 characters.

All the basic operations with the database are accomplished using the SQLiteAsyncConnection class, which exposes asynchronous methods to create tables, query the data, delete items, etc. It requires as an input parameter the local storage path where the database will be saved.

As with SQL CE and LINQ to SQL, we need to create the database before using it. This is done by calling the CreateTableAsync<T>() method for every table we need to create, where T is the table’s type. In the following sample, we create a table to store the Person entity:

We don’t have a method to verify whether the table already exists since it’s not needed; if the table we’re creating already exists, the CreateTableAsync<T>() method simply won’t do anything.

In a similar way to LINQ to SQL, queries are performed using the Table<T> object. The only difference is that all the LINQ methods are asynchronous.

In the previous sample, we retrieve all the Person objects whose name is Matteo.

Insert, update, and delete operations are instead directly executed using the SQLiteAsyncConnection object, which offers the InsertAsync(), UpdateAsync(), and DeleteAsync() methods. It is not required to specify the object’s type; sqlite-net will automatically detect it and execute the operation on the proper table. In the following sample, you can see how a new record is added to a table:

Sqlite-net is the SQLite library that offers the easiest approach, but it has many limitations. For example, foreign keys are not supported, so it’s not possible to easily manage relationships.

SQLite Wrapper for Windows Phone

SQLite Wrapper for Windows Phone has been developed directly by Microsoft team members (notably Peter Torr and Andy Wigley) and offers a totally different approach than sqlite-net. It doesn’t support LINQ, just plain SQL query statements.

The advantage is that you have total control and freedom, since every SQL feature is supported: indexes, relationships, etc. The downside is that writing SQL queries for every operation takes more time, and it’s not as easy and intuitive as using LINQ.

To learn how to configure the wrapper in your project, follow the instructions posted on the CodePlex project page. You’ll have to download the project’s source code and add the correct wrapper version to your solution—there are two separate libraries, one for Windows Phone 8 and one for Windows Store apps.

The key class is called Database, which takes care of initializing the database and offers all the methods needed to perform the queries. As a parameter, you need to set the local storage path to save the database. If the path doesn’t exist, it will be automatically created. Then, you need to open the connection using the OpenAsync() method. Now you are ready to perform operations.

There are two ways to execute a query based on the value it returns.

If the query doesn’t return a value—for example, a table creation—you can use the ExecuteStatementAsync() method as shown in the following sample:

The previous method simply executes the query against the opened database. In the sample, we create a People table with two fields, Name and Surname.

The query, instead, can contain some dynamic parameters or return some values. In this case, we need to introduce a new class called Statement as demonstrated in the following sample:

The Statement class identifies a query, but it allows additional customization to be performed with it. In the sample, we use it to assign a dynamic value to the Name and Surname parameters. We set the placeholder using the @ prefix (@name and @surname), and then we assign them a value using the BindTextParameterWithName() method, passing the parameter’s name and the value.

BindTextParameterWithName() isn’t the only available method, but it’s specifically for string parameters. There are other methods based on the parameter’s type, such as BindIntParameterWithName() for numbers.

To execute the query, we use the StepAsync() method. Its purpose isn’t just to execute the query, but also to iterate the resulting rows.

In the following sample, we can see how this method can be used to manage the results of a SELECT query:

The StepAsync() method is included inside a while statement. At every loop iteration, we’ll get the reference to the next row returned by the query, starting from the first one. After we’ve iterated all the rows, the application will quit the while loop.

When we have a row’s reference, we can access its values by using the column index and the Get() method. We have a Get() variant for every data type, like GetText(), GetInt(), etc.

Another way is to access the columns using the Columns collection with the column name as the index. In this case, you first have to call the EnableColumnsProperty() method, as shown in the following sample:

Keep in mind that this approach is slower than using the column’s index.

Conclusion

This article delivered more key concepts that every Windows Phone developer should be familiar with. Managing local data is important, and in this article we’ve discussed the following approaches:

  • Correctly using files and folders in the isolated storage thanks to the Windows Runtime APIs.
  • Easily managing our application’s settings by using the IsolatedStorageSettings class.
  • Storing our application’s data using serialization and deserialization in simple app scenarios.
  • In case of more complex applications, we’ve seen how we can better organize our data using databases. We analyzed two available solutions: SQL CE and SQLite. They both offer a stand-alone database platform. SQL CE is exclusive to Windows Phone, but it is more powerful and easier to use; SQLite is open source and cross-platform, but you have to rely on third-party libraries which aren’t as powerful as LINQ to SQL for SQL CE.

This tutorial represents a chapter from Windows Phone 8 Succinctly, a free eBook from the team at Syncfusion.

2015-03-12T08:30:46.000Z2015-03-12T08:30:46.000ZMatteo Pagani

Viewing all articles
Browse latest Browse all 1836

Trending Articles