(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows Phone 7.5 - Local SQL Database

(6 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
10 comments   /   posted on Oct 03, 2011
Categories:   Windows Phone
Tweet This!

Once you start programming Windows Phone just after the first experiments, when you begin writing something of real, immediately you need something to store information locally.

Also if the phone is a mobile device and it is almost always connected, there are information you cannot store remotely but you need to have always available. I'm speaking for sure about configuration settings but also about locally cached relational data that is required to run the application fast, without continuously access the network.

In Windows Phone 7.0 the sole storage available was the Isolated Storage but it appear like a filesystem and it requires lot of code to serialize and deserialize entities. Some open source solutions has been created to work around this problem. I for sure remember Sterling, a real object oriented no-sql database that I used often to write my own applications. In the upcoming release of Windows Phone 7.1, Microsoft decided to add a Local Database implementation that is targeted to business applications and is created on top of SQL Server CE. This new feature completes the storage solutions available for the phone, supporting scenario where you need a real relational database and is really easy to configure and use. It does not totally override other solutions. Sterling remains a good solution for many scenarios, but having a real relational store is almost wonderful in many cases.

How does it work.

As I've said, the new Local Database available in Windows Phone 7.1, is based on the SQL Server Compact Edition, a well known and popular edition of SQL Server, originally created to support Windows Mobile and Windows CE with a local relational store. Due to the fact that under the hoods of the Windows Phone 7.x there is Microsoft .NET Compact Framework 3.7, SQL CE is a obvious choice and it is for sure a solid and realiable tool to support real world applications.

But, as you know, the Silverlight API in Windows Phone 7 does not includes ADO.NET so the access to a SQL CE database cannot be done using normal SQL queries. For this purpose the WP7's team taken the existing LinqToSQL code and ported it to the phone. This means that to access the SQL CE storage you have to use linq queries and the DataContext to modify and extract information from the database. LinqToSQL is not a real object oriented database. It use a simple attribute-based mapping that does not abstract so much the relational structure to a real consistent domain model and this is the reason why Microsoft decided to override it with the Entity Framework, but for the purpose of creating and accessing a local database it is a good, easy and realiable choice.

A SQL CE Database is usually associated with SDF files. In Windows Phone SDF still exists but they are placed into the isolated storage so the first thing you notice is a special connection string format that follow this pattern:

isostore://file.sdf
 

As a side consideration, having the sdf in isolated storage implies that the access to the file is strictly related to the application that creates it. With this release there is not any way of sharing a database between multiple application without duplicating it across different isolated storage directories.

From the developer point of view, working with a SQL CE database means adding a reference to an assembly (System.Data.Linq.dll). Once the reference is added there is not any requirement of deploying SQL CE runtime, because it is part of the Windows Phone 7.1 so it does not increase the size of your application.

Mapping entities to tables

In LinqToSQL there is a direct association between tables and entities so you will always have an entity mapped to each table, also in many-to-many scenarios where the relation table is represented by a real entity instead of two crossing collection as you expect. So in a Product/Order domain you will have also a ProductsOrders entity representing the many-to-many relation between the Product and Order entities.

The mapping of entities then, is really simplified because you have to create a class for each table, and always one-to-many associations. For this purpose you have a small set of attributes you can apply to the classes. TableAttribute to describe the table, ColumnAttribute for the fields and AssociationAttribute for relationships. Your entities are by default POCO object so you can add your own inheritance for the purposes of you application. As an example you can implement INotifyPropertyChanged to support databinding but you can also derive from base classes for same specific scenario. The following snippet shows an entity related to a feed with a bunch of properties mapped:

 1: [Table(Name = "Feeds")]
 2: public abstract class Feed
 3: {
 4:     [Column(IsPrimaryKey = true)]
 5:     public int ID { get; set; }
 6:     [Column]
 7:     public string Title{ get; set; }
 8:     [Column]
 9:     public string Description{ get; set; }
 10:     [Column]
 11:     public string Address{ get; set; }
 12:     [Column]
 13:     public DateTime DateCreated { get; set; }
 14: }
Since the TableAttribute simply names the table on the database,  the ColumnAttribute is able to determine the behavior of the field. It has a number of properties defined. In this snippet you see I've defined the primary key using the IsPrimaryKey property.
To map associations you have to work on both sided of the relationship. This means adding a collection property on the "many" side and a reference property ong the "one" side. Here is an example of the relation between Feed and Post where Feed has many Posts:
 1: // on the Feed side //////////////////////////////////////////////////////////////
 2:  
 3: private readonly EntitySet<Post> postsRef = new EntitySet<Post>();
 4:  
 5: [Association(Name = "FK_Feed_Posts", Storage = "postsRef", ThisKey = "ID", OtherKey = "FeedID")]
 6: public EntitySet<Post> Posts
 7: {
 8:     get { return this.postsRef; }
 9: }
 10:  
 11: // on the Post side //////////////////////////////////////////////////////////////
 12:  
 13: private EntityRef<Feed> feedRef = new EntityRef<Feed>();
 14:  
 15: [Association(Name = "FK_Feed_Posts", Storage = "feedRef", ThisKey = "FeedID", OtherKey = "ID", IsForeignKey = true)]
 16: public Feed Feed
 17: {
 18:     get { return this.feedRef.Entity; }
 19:     set
 20:     {
 21:         Feed previousValue = this.feedRef.Entity; 
 22:         
 23:         if (previousValue != value || this.feedRef.HasLoadedOrAssignedValue == false)
 24:         {
 25:             if (previousValue != null)
 26:             {
 27:                 this.feedRef.Entity = null; 
 28:                 previousValue.Posts.Remove(this);
 29:             } 
 30:             
 31:             this.feedRef.Entity = value; 
 32:             
 33:             if (value != null)
 34:             {
 35:                 value.Posts.Add(this);
 36:                 this.FeedID = value.ID;
 37:             } 
 38:             else
 39:             {
 40:                 this.FeedID = default(int);
 41:             }
 42:         }
 43:     }
 44: }

Expecially the Post side, it requires a number of lines of code, but since the code is always the same I suspect you can work to create a Visual Studio snippet to shorten and automate its creation.

The mapping is for sure simple and probably it sounds trivial to a refined palate used to the complex mapping of modern ORMs. However it reveal some surprises if you go deep analyzing the model. As LinqToSQL it supposts a raw Inheritance "table-per-hierarchy" mapping. For people that is not use to ORMs lingo, "table-per-hierarchy" is the simpler model for mapping inheritance of entities. It involve to have a single table mapping all the inherited entities so the fields that are not in common between inherited entities have to be nullable. Here is how to map inheritance:

 1: [Table(Name = "Feeds")]
 2: [InheritanceMapping(Code = "ATOM", Type = typeof(AtomFeed))]
 3: [InheritanceMapping(Code = "RSS", Type = typeof(RssFeed), IsDefault = true)]
 4: public abstract class Feed
 5: {
 6:     [Column(IsDiscriminator = true)]
 7:     public string Type{ get; set; }
 8:  
 9:     // other properties here (these are common to other types
 10: }
 11:  
 12: public class AtomFeed : Feed
 13: {
 14:     [Column(CanBeNull = true)]
 15:     public string Email { get; set; }
 16: }
 17:  
 18: public class RssFeed : Feed
 19: {
 20:     [Column(CanBeNull = true)]
 21:     public string Generator { get; set; }
 22: }

As you can see AtomFeed and RssFeed share all the properties of the base class but add other properties that are specific to each type. These are decorated with "CanBeNull" property because the row on the table will always contains all the properties of all the inherited entities. It is not the optimal solution but it is better than nothing.

Creating the database

Also if at the basis of SQL CE there is SDF files, you should never directly access them but you have to manage you database directly from code. If you search the internet you will find some examples to download the SDF from the phone and the upload it again, but in this article I will cover the official way. To me the creation of the structure of the database is very straightforward using code but I think someone can take advantage of the direct access to the SDF providing pre-populated databases.

The last thing you have to do before creating the physical database, just after creating entities, is a special class called DataContext. The DataContext wraps all the operations you can do with a database like inserting, updating and deleting data and exposes a number of sets, one for each "table" you have in the SDF. The DataContext appear like this:

 1: public class FeedReaderDataContext : DataContext
 2: {
 3:     public const string ConnectionString = "isostore:/feeds.sdf";
 4:  
 5:     public FeedReaderDataContext(string connectionString)
 6:         : base(connectionString)
 7:     {
 8:         this.Feeds = this.GetTable<Feed>();
 9:         this.Posts = this.GetTable<Post>();
 10:     }
 11:  
 12:     public Table<Feed> Feeds { get; set; }
 13:     public Table<Post> Posts { get; set; }
 14: }

In this example I show a datacontext made to store blog Feeds and Posts so in the first line I have a constant string with the connection string "isostore://feeds.sdf". Then I have two properties mapping the sets. A set is a collection created with the Table<T> class and gives a name to the "table" in the database. In the constructor I call the GetTable<T>() method that connects the DataContext sets with the SDF tables.

After the DataContext is in place you have to write code to create the database if it is not already in the isolated storage. For this purpose you have the CreateDatabase() method and the DatabaseExists() method. The pattern I use for the data access is creating a repository class that wraps the DataContext with my custom data access methods. In the repository I've a static method "Initialize" that is in charge of checking the existing database and eventually create it.

 1: public class FeedReaderRepository
 2: {
 3:     public static void Initialize()
 4:     {
 5:         using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 6:         {
 7:             if (dc.DatabaseExists() == false)
 8:             {
 9:                 dc.CreateDatabase();
 10:  
 11:                 // add here extra initialization like creating required rows
 12:  
 13:                 dc.SubmitChanges();
 14:             }
 15:         }
 16:     }
 17: }

In place of the comment I'm use to add rows that I expect already exist into the new database, like lookup records and initialized data. The operation of checking the database must be made at the beginning of the life of the application just before it starts accessing rows. So the better place is the App class constructor where I add the following line:

FeedReaderRepository.Initialize();
 

When you deploy an application it is expected you make some changes to the database during the application lifecycle. It is almost impossible that the first database you create on the phone is able to support all the future scenarios. For this purpose Windows Phone 7.1 provides a DatabaseSchemaUpdater class. It lets you access a version number for the deployed schema and a bunch of methods that supports the creation of new tables, columns, indexes and relations. As far I can see there is nothing that let you delete something in existing databases.

Querying the data

Now that your mapping is created and the database has been deployed to the device it is time to start querying the data. The pattern is you create the DataContext instance, pointing to the right database, then use the sets to make queries and the Insert and Delete methods to make changes. Using linq is really simple and well documented so in this paragraph I will only show the basic concepts. To deal with joins, grouping and other advanced operations please refer to the LinqToSQL documentation:

 1: public IEnumerable<Feed> GetRecentFeeds()
 2: {
 3:     using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 4:         return dc.Feeds
 5:             .OrderByDescending(f => f.DateCreated)
 6:             .Take(2)
 7:             .ToArray();
 8: }
 9:  
 10: public bool FeedExists(string address)
 11: {
 12:     using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 13:     {
 14:         return (from fd in dc.Feeds
 15:                 where fd.Address == address
 16:                 select fd).Count() > 0;
 17:     }            
 18: }

In the above sample I've put togheter two queries. The first select the most recent feeds taking the first two from a list orderer descending by the column DateCreated. The important thing to remember is to always call ToArray() or ToList() before returning the entities because it performs the query. Returning the reqult of the Take(2) does not returns the entities but a query that cannot be executed outside of the scope of the DataContext. So enumerating the query will raise an exception if you do not call the ToArray method here. The other query search for a field by the address string and verify it exists. In this case the Count() method perform the query and the return value will be a scalar that is not connected with the Datacontext.

To update entities you have to make changed to objects you have extracted from the DataContext. When you make the changes they are tracked and calling the SubmitChanges() method will persist all the changes in a unique batch. This also applies to Insert and Delete that map to the InsertOnSubmit and DeleteOnSubmit methods. This way you can watch at the DataContext as an implementation of the Unit-Of-Work pattern. You make changed then, at the end, you call SubmitChanges to persist to the database. Here is some examples:

 1: public void DeleteFeed(Feed feed)
 2: {
 3:     using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 4:     {
 5:         feed.DateCreated = DateTime.Now;
 6:         dc.Feeds.DeleteOnSubmit(feed);
 7:         dc.SubmitChanges();
 8:     }
 9: }
 10:  
 11: public void SaveFeed(Feed feed)
 12: {
 13:     using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 14:     {
 15:         feed.DateCreated = DateTime.Now;
 16:         dc.Feeds.InsertOnSubmit(feed);
 17:         dc.SubmitChanges();
 18:     }
 19: }
 20:  
 21: public void UpdateFeedDate(int id, DateTime date)
 22: {
 23:     using (FeedReaderDataContext dc = new FeedReaderDataContext(FeedReaderDataContext.ConnectionString))
 24:     {
 25:         var feed = (from f in dc.Feeds
 26:                     where f.ID == id
 27:                     select f).FirstOrDefault();
 28:  
 29:         if (feed != null)
 30:         {
 31:             feed.DateLastUpdate = date;
 32:             dc.SubmitChanges();
 33:         }
 34:     }
 35: }

This is the beautiful part of the game. Linq is the most flexible tool for querying the data and it is plenty of examples on the Internet if you need help to extract the data in the way you prefer. Refer to this page for a number of samples.

Apply indexes

Everyone have used a database knows the importance of indexes. A local database on SQL CE probably will never contain million of records, nevetheless sometimes an index can make the difference when you need to search with a where  clause or when you need to sort a set using order by. In Windows Phone 7.1 you can easily apply indexes to tables and the underlying SQL CE engine will manage them transparently, without the need of wiring any code to handle them.

To apply an index you have simply to use the IndexAttribute, at the entity level, specifying the columns to which it applies, its name and the uniqueness constraint.

 1: [Table(Name = "Feeds")]
 2: [Index(Columns = "DateCreated", IsUnique = false, Name = "Feed_DateCreated")]
 3: public abstract class Feed : ObservableObject
 4: {
 5:     /// ...
 6: }

Index are created on the database when you call the CreateDatabase() method then are completely managed by SQL CE. If you need an additional index you can call the AddIndex method on the DatabaseSchemaUpdater class.

When to use SQL CE

SQL CE is a simple database and for sure it will power up applications developed for Windows Phone 7.1. You may consider using it both when you need to store something a bit complex than a configuration, but also in scenarios where you need to cache data for a smart application to continue working while it is offline. In both the cases it is a good solution able to speed up you development and thanks to the updating features you are able to maintain your application, adding features during the application lifecyle, in a reliable way.

Download the source code

  

All SilverlightShow newsarticlesfree webinars and ebooks are available via Follow us on twitter Become a
SilverlightShow fan on Facebook Join
SilverlightShow Group on LinkedIn

  


Subscribe

Comments

  • SaadHaffar

    Re: Windows Phone 7.1 - Local SQL Database


    posted by SaadHaffar on Oct 09, 2011 19:47
    Great post  !! Thanks for your sharing  !!
  • brauliodiez

    Re: Windows Phone 7.5 - Local SQL Database


    posted by brauliodiez on Nov 07, 2011 13:07

    Very good stuff Andrea !!!

    Very well written article, about the sample good guidance.

    Just one thing I download the sample as is and got an exception when launching it (DateTime overflow exception), I will take a look.

    For newcomers, maybe it would be worth to first read (http://devlicio.us/blogs/derik_whittaker/archive/2011/07/21/using-sql-ce-on-wp7-mango-getting-started.aspx) then jump into this comple and great article from Andrea.

  • brauliodiez

    Re: Windows Phone 7.5 - Local SQL Database


    posted by brauliodiez on Nov 07, 2011 13:18

    I think I got it, when inserting test data:

    InsertOnSubmit was not informing DateLastUpdate

    That was why it was crashing, mmm... maybe no default value leads to that crash? (or maybe just set that value to nullable?)

    Cheers

      Braulio

     

     

     

     

     

     

    DateTime.Now

     

  • MarcoAntonioAguilar

    Re: Windows Phone 7.5 - Local SQL Database


    posted by MarcoAntonioAguilar on Feb 05, 2012 18:54

    Tengo un ERROR al ejecutar la aplicación, me manda un erro de desbordamiento al convertir el datetime, Porque pasa eso?

  • ShaniBaruch

    Re: Windows Phone 7.5 - Local SQL Database


    posted by ShaniBaruch on Apr 02, 2012 13:04

    hi,

    My sql is at the visual studio 2010 , I create it there.

    How can i connect my sql to the code that you write, it doesn't work, i can't see the things that i add to the data base, the data base is empty .

    please help me!

  • ShaniBaruch

    Re: Windows Phone 7.5 - Local SQL Database


    posted by ShaniBaruch on Apr 02, 2012 13:57

    hi,

    My sql is at the visual studio 2010 , I create it there.

    How can i connect my sql to the code that you write, it doesn't work, i can't see the things that i add to the data base, the data base is empty .

    please help me!

  • dvernole

    Re: Windows Phone 7.5 - Local SQL Database


    posted by dvernole on Apr 15, 2012 21:03
    Nice work as usual. I have changed the DateTime properties of the objects to nullable type to avoid runtime errors. HTH
  • AndreaBoschin

    Re: Windows Phone 7.5 - Local SQL Database


    posted by AndreaBoschin on Apr 16, 2012 00:53
    Thanks for the suggestions! Bye.
  • -_-

    Re: Windows Phone 7.5 - Local SQL Database


    posted by on Mar 12, 2013 21:35
    Here is a database compatible with Windows Phone 8:
    https://www.kellermansoftware.com/p-49-ninja-winrt-database.aspx
  • akindroid

    Re: Windows Phone 7.5 - Local SQL Database


    posted by akindroid on Jun 02, 2013 02:53

    Sorry am getting this 

    An overflow occurred while converting to datetime.

     

    How can i fix this.

    Urgent Thanks

Add Comment

Login to comment:
  *      *       

From this series