Refactoring the IsolatedStorageORM

During the course of the week I got a chance to attend to some of the glaring issues with the framework for managing Isolated Storage on Windows Phone 7.  I am quite pleased with what I came up with.  It permits a good amount of flexibility where needed while still leveraging enough code  in the core to carry out complex operations

Repository Types
The first thing I started looking at was the idea of a singular repository.  When dealing with phone data, for many things you arent talking about multiple users; phones tend to be single user devices so you arent likely to store a bunch of user profiles governing the settings for your application for a number of users.  This concept eventually came though as RepositorySingle to join RepositoryCollection.

IDataHandler
As with the original, the developer writes the actual repository class and inherits from RepositoryBase which will provide the functionality needed to use it with the framework; an example implementation is shown below:

   1: public class IsolatedStorageRepository : RepositoryBase

   2: {

   3:     public RepositoryCollection StorageItems { get; set; }

   4:     public RepositorySingle Options { get; set; }

   5:  

   6:     public IsolatedStorageRepository()

   7:     {

   8:         StorageItems = RepositoryFactory.

   9:             GetRepositoryCollectionInstance("sampleData");

  10:         Options = RepositoryFactory.

  11:             GetRepositorySingleInstance("settings2");

  12:     }

  13: }

While this code looks similar to the code in the prototype, it is actually quite different under the hood.  The main thing was the introduction of IDataHandler which provides method for reading and writing objects from the underlying persistence mechanism; yes the framework can work with other storage mechanisms now. RepositoryFactory contains two overloads for each of the getters. GetRepositoryCollectionInstance is shown below

   1:  

   2:         public static RepositoryCollection GetRepositoryCollectionInstance

   3:             (IDataHandler service) where T : EntityBase, new()

   4:         {

   5:             return new RepositoryCollection(service);

   6:         }

   7:  

   8:         public static RepositoryCollection GetRepositoryCollectionInstance

   9:             (string filepath) where T : EntityBase, new()

  10:         {

  11:             return GetRepositoryCollectionInstance(

  12:                 new IsolatedStorageDataHandler(filepath));

  13:         }

The IDataHandler was introduced to allow me to run my unit tests against the code without a phone behind it and, as it turns out, you cant have IsolatedStorage just cause Smile.  So the gist here is, you can specify your own if you like or you can simply pass the file path and it is assumed that you want IsolatedStorage.

To create your own, you would simply implement IDataHandler, optionally you can inherit from the abstract class DataHandlerBase which will provide some helpful methods for dealing with files.  This is an area that I am targeting for refactor next.

Converters and Attributes
One of the major limitations with the original version was it only supported string properties on any given object.  With this refactor I introduced the concept of a converter associated with the attributes.  The new DataColumnAttribute is now the base class for all typed data column attributes.  Currently, I have added support for int, short, long, boolean, and DateTime.  The entities now look like such:

   1: [DataColumn(0)]

   2: public string FirstName { get; set; }

   3:  

   4: [DataColumn(1)]

   5: public string LastName { get; set; }

   6:  

   7: [DateTimeDataColumn(2)]

   8: public DateTime DateOfBirth { get; set; }

   9:  

  10: [BooleanDataColumn(3)]

  11: public bool IsAlive { get; set; }

 

So now when you use the vanilla DataColumn attribute, the value stored at this column is assumed to be a string.  As you can see we create derivatives to support the various types, the following is the source for BooleanDataColumnAttribute:

   1: public sealed class BooleanDataColumnAttribute

   2:     : DataColumnAttribute

   3: {

   4:     public BooleanDataColumnAttribute(int index)

   5:         : base(index)

   6:     {

   7:         Converter = new BooleanConverter();

   8:     }

   9: }

As you can see, the Converter is simply assigned in the constructor.  The only requirement is it MUST inherit from IConvertFromString which is publically exposed to allow the creation of custom Converters, for example, in a project I am doing I have the DisplayPaneDataColumnAttribute class:

   1: public class DisplayPaneDataColumnAttribute

   2:     : DataColumnAttribute

   3: {

   4:     public DisplayPaneDataColumnAttribute(int index)

   5:         : base(index)

   6:     {

   7:         Converter = new DisplayPaneConverter();

   8:     }

   9: }

And the source for DisplayPaneConverter:

   1: public class DisplayPaneConverter : IConvertFromString

   2: {

   3:     #region Implementation of IConvertFromString

   4:  

   5:     public object FromString(string s)

   6:     {

   7:         if (Enum.IsDefined(typeof(DisplayPane), s))

   8:             return s.AsEnum();

   9:  

  10:         throw new ArgumentException("...");

  11:     }

  12:  

  13:     #endregion

  14: }

Granted I do not like to require the developer extending the attribute to have to make the assignment, I feel that is the job of the framework, might be a possible refactor.

Usage
So with these new changes what does a use case look like?  Well it gets to be very simple and much of the code is removed and stored away.  Below is an example using RepositorySingle where application settings are stored and read:

   1: public partial class Settings : PhoneApplicationPage

   2: {

   3:     private MyRepository repository =

   4:         new MyRepository();

   5:  

   6:     public Settings()

   7:     {

   8:         InitializeComponent();

   9:         Loaded += Settings_Loaded;

  10:     }

  11:  

  12:     private void Settings_Loaded(object sender, RoutedEventArgs e)

  13:     {

  14:         loopingDefaultPane.DataSource.SelectedItem =

  15:             repository.ApplicationSettings.Single.DefaultDisplayPane;

  16:         loopingRefresh.DataSource.SelectedItem =

  17:             repository.ApplicationSettings.Single.RefreshInterval;

  18:     }

  19:  

  20:     private void btnSave_Click(object sender, RoutedEventArgs e)

  21:     {

  22:         repository.ApplicationSettings.Single.DefaultDisplayPane =

  23:             (DisplayPane) loopingDefaultPane.DataSource.SelectedItem;

  24:  

  25:         repository.ApplicationSettings.Single.RefreshInterval =

  26:             loopingRefresh.DataSource.SelectedItem.ToString().AsInt();

  27:  

  28:         repository.SaveChanges();

  29:     }

  30: }

The next step with this code is to abstract out the direct usage of repository instantiation.  Preventing this is, as of right now, I am loading all data each time a new instance of the repository is created, I don’t like this and I am seeking a way for the instances to share data so that it doesn’t have to be loaded and unloaded all the time.

Conclusion
As with any framework some iterations are required.  I want to clean up some last bits of inefficient code.  The main goal is writing tests to confirm the operation on the various pieces, however, this is difficult.  Much of the code for this is internalized to the given assembly, thus creating external unit tests is difficult.

I do look for this to be something I can use in the future, especially with mobile applications, to assist with working with data in IsolatedStorage.  I am providing a download of my test project in Visual Studio 2010 format.

http://cid-630ed6f198ebc3a4.office.live.com/embedicon.aspx/Source/RelationalIsolatedStorage.zip

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s