Building a simple Windows Phone App – Part 3

by Alex on February 8, 2012

Disclaimer: I’m learning about windows phone and keeping a log of that experience here. I fully expect that some of the things I do here will be wrong. Copy code at your own risk.

In an effort to keep the posts in this series shorter than a dissertation, I wont be covering every class and method in detail, just some of the stuff that I think is interesting. You can download the code here and feel free to contact me with any comments/questions.

This is the third post in a series that I’m writing on how to create a simple windows phone application.  You can read posts one and two here:

Building a simple Windows Phone App – Part 1

Building a simple Windows Phone App – Part2

If you don’t want to take the time to read parts one and two, here is a quick recap.

In part one I reviewed my design process both on paper and in Expression Blend and generally just prepped for the main event: building the app. Then in part 2 I covered the tools I would be using and got busy on the development. The app uses the MVVM pattern and hides its data access implementation behind an interface to make it easy to swap out. The reason for the interface was that the intention from the beginning of this journey was to create 3 versions of the app. Version one stores its data in an XML file in Isolated Storage. Version 2 stores data in a sql database on the phone and version 3 stores the data up in the cloud and is accessed over the wire.

OK, with that out of the way, lets get on to the Sql Server version of the TaskMaster App.

Modifying this app to access sql server is a pretty trivial task.  The steps include:

1. Create the DataContext.

2. Create the Model and decorate it with the appropriate attributes so that the database table is created correctly.

3. Create a helper class to populate the database with some test data so we have something to look at when the app runs.

4. Modify the ViewTaskViewModel so that each time an item is added or deleted from the database, all the items do not need to be reloaded, we simply update the ObservableCollections in memory.

5. Modify the App.xaml.cs so when the app starts up, we check if DB exists. If not, we create it and populate it.

Lets take a look at each of these steps in turn

1. Create the DataContext

If you’ve ever done any work with LINQ to SQL or the Entity Framework in the past this class should look familiar to you. Essentially you can think of the DataContext as your gateway to the database. It also provides a lot of functionality for tracking changes to objects so that you don’t have to. In order to create a data context for our  little phone app, we need to create a new class that inherits from DataContext.

public class TaskMasterDataContext : DataContext
{
    public TaskMasterDataContext() : base("Data Source=isostore:/TaskMasterData.sdf")
    {
    }
    public Table<Task> Tasks;
}

2. Create Model Object

Now we need to create our model class. Considering the simplicity of this app, we only have one model class, Task.

namespace TaskMaster.Models
{
    [Table]
    public class Task : INotifyPropertyChanged, INotifyPropertyChanging
    {
        [Column(IsDbGenerated = false, IsPrimaryKey = true, CanBeNull = false)]
        public string Id
        {
            get { return _id; }
            set
            {
                NotifyPropertyChanging("Id");
                _id = value;
                NotifyPropertyChanging("Id");
            }
        }

        [Column]
        public string Name
        {
            get { return _name; }
            set
            {
                NotifyPropertyChanging("Name");
                _name = value;
                NotifyPropertyChanged("Name");
            }
        }

        [Column]
        public string Category
        {
            get { return _category; }
            set
            {
                NotifyPropertyChanging("Category");
                _category = value;
                NotifyPropertyChanged("Category");
            }
        }

        [Column]
        public DateTime? DueDate
        {
            get { return _dueDate; }
            set
            {
                NotifyPropertyChanging("DueDate");
                _dueDate = value;
                NotifyPropertyChanged("DueDate");
            }
        }

        [Column]
        public DateTime? CreateDate
        {
            get { return _createDate; }
            set
            {
                NotifyPropertyChanging("CreateDate");
                _createDate = value;
                NotifyPropertyChanged("CreateDate");
            }
        }

        [Column]
        public bool IsComplete
        {
            get { return _isComplete; }
            set
            {
                NotifyPropertyChanging("IsComplete");
                _isComplete = value;
                NotifyPropertyChanged("IsComplete");
            }
        }

        [Column(IsVersion = true)] private Binary _version;

        private string _id;
        private bool _isComplete;
        private DateTime? _createDate;
        private DateTime? _dueDate;
        private string _name;
        private string _category;
        public event PropertyChangedEventHandler PropertyChanged;
        public event PropertyChangingEventHandler PropertyChanging;

        public void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }

        public void NotifyPropertyChanging(string property)
        {
            if (PropertyChanging != null)
                PropertyChanging(this, new PropertyChangingEventArgs(property));
        }
    }
}

There are a couple of things to take note of on this model class. First of all, you can see that the class is decorated with the Table Attribute from the System.Data.Linq.Mapping namespace. Further, each of the properties are decorated with the Column attribute.  These attributes tell our DataContext class how we want this table to be created in sql server.

You can see that we have added a Binary property called _version. The property was added to this class solely for performance reasons related to LINQ to Sql. Word on the street is that just by adding this column, your app will get as much as a 7 times performance improvement on a single update of 100 rows of data. The reason for this is as follows: (borrowed directly from Jesse Liberty’s blog)

LINQ-to-SQL is based on optimistic concurrency, which means that possible inconsistencies caused by concurrent users updating the database is checked for only when you submit the transaction (there is no prophylactic record locking).  By default, LINQ to SQL submits your changes to the database query processor, which does the consistency check.

The performance enhancement comes by ignoring the query processor and working directly with the tables.  But this means that there has to be another way to determine if the record has changed since the previous query. Enter the version column; if this has not changed, then the entire transaction is safe. If it has changed, of course, then LINQ to SQL will throw a ChangeConflictException, but it would have done that anyway.

In short, by adding a version column, only one column needs to be tested, and performance takes a big boost.

Can you say no-brainer Batman? Add this property to your model objects. There’s no reason not to.

Finally, lets talk about INotifyPropertyChanged and INotifyPropertyChanging. Typically we implement INotifyPropertyChanged on our view models to keep the data in the object and on the UI in sync, but what’s the deal with INotifyPropertyChanging?  This works hand in hand with the version column that we just discussed.  LINQ to Sql must perform change tracking on the objects in memory so that it knows what to update when SubmitChanges() is called. By implementing INotifyPropertyChanging and raising this event BEFORE you execute the code in your setter, LINQ to Sql will know what columns have changed and will only update those columns at the appropriate time. If we don’t implement this interface, it will update all columns even if only one of them has changed.  Holy wasteful Batman! POW.

3. Create a helper class to populate the database

this step isn’t completely necessary but its nice to have some data to look at when the app spins up and its trivial to implement.

public static class DatabaseHelper
{
    public static void SetupDatabase(TaskMasterDataContext dataContext)
    {
        string category = string.Empty;
        var tasks = new List<Task>();
        for (int i = 0; i < 20; i++)
        {
            tasks.Add(new Task()
                          {
                              Id = System.Guid.NewGuid().ToString(),
                              Category = GetCategoryString(i),
                              CreateDate = DateTime.Now,
                              DueDate = DateTime.Now.AddDays(new Random().Next(1, 30)),
                              IsComplete = false,
                              Name = String.Format("{0} {1}", GetCategoryString(i), i)
                          });
        }
        dataContext.Tasks.InsertAllOnSubmit(tasks);
        dataContext.SubmitChanges();
    }

    private static string GetCategoryString(int i)
    {
        if (i%2 == 0)
            return "home";

        if (i%3 == 0)
            return "personal";

        return "work";
    }
}

As you can see, this is a very straightforward class. The constructor takes one of our recently created DataContext objects, enters a for loop and shoves in 20 rows of data. I have a little helper method  called GetCategoryString() to make sure I get some tasks in each category.

4.  Modify the TaskViewViewModel

This step harkens back to a comment I made in the previous post.  I noted at that time that I felt like I was accessing the data store too often and needed  to change the way I was implementing my read/write procedures. Well, that’s what I’ve done in this version.  Now, in my ViewTaskViewModel, when I mark a task complete or delete it, I only make one DB call and remove or update the item in the ObservableCollection. That way the UI gets updated but we don’t reload the data. I’m still not entirely sure this is the best way to go about it but it sure feels better to me. Here’s some of the code:

public bool MarkTaskComplete(string taskId)
{
    bool val = TaskService.MarkTaskComplete(taskId);
    var t = Tasks.FirstOrDefault(task => task.Id == taskId);

    //Remove the task from its category OC 
    RemoveTaskFromCategoryCollection(t);

    //remove it from the "all tasks" OC
    Tasks.Remove(t);

    //add it to the completed collection
    CompletedTasks.Add(t);

    return val;
}

public void DeleteTask(string id)
{
    //get the task in question
    var t = CompletedTasks.FirstOrDefault(task => task.Id == id);
    //remove it from the "all tasks" list
    Tasks.Remove(t);

    //remove it from the completed tasks
    CompletedTasks.Remove(t);

    //delete it from the db
    TaskService.DeleteTask(id);
}

private void RemoveTaskFromCategoryCollection(Task task)
{
    switch (task.Category)
    {
        case "home":
            HomeTasks.Remove(task);
            break;
        case "personal":
            PersonalTasks.Remove(task);
            break;
        case "work":
            WorkTasks.Remove(task);
            break;
        case "completed":
            CompletedTasks.Remove(task);
            break;
    }
}

5. Modify App.xaml.cs

OK, we coming into the home stretch. The last thing we need to do is modify app.xaml.cs so that when the app fires up, we new up a DataContext and check to see if our database exists. If it does not, we create it and fill it with some dummy data.

//used when storing and accessing task related data in an XML file
//in Isolated Storage
//_taskService = new XmlTaskService();

//Use when storing data in Sql Server
_taskService = new SqlTaskService();

TaskMasterDataContext = new  TaskMasterDataContext();

if (!TaskMasterDataContext.DatabaseExists())
{
    TaskMasterDataContext.CreateDatabase();
    DatabaseHelper.SetupDatabase(TaskMasterDataContext);
}

This little snippet if from the constructor in App.xaml.cs. If you recall from the previous post, we hid the data access implementation behind an interface, ITaskService. You can see above that I’ve commented out XmlTaskService and now we’re using SqlTaskService.   This is the reason I did it this way. Just by changing this one line of code, we can switch from the XML data store to Sql. Easy.

Next we just create a new TaskMasterDataContext and assign it to a public property that we have defined in the App class. We then proceed to create the DB and populate it.

In the next and final post of this series, we’ll review getting our data from a service on the web.

You can download the source code for this post here.

Thanks for visiting.

{ 4 comments… read them below or add one }

souphia March 24, 2012 at 4:53 pm

thanks for your blog, please i want to know that if i copy the .sdf file from isolate storage to installation folder how to insert, i think your SetupDatabase method is for that reason but how it works. I mean that when we copy the .sdf file to project with content it means that if database updated in isolate storage update our .sdf file from our project also? am i true or please explain if not..
thanks

Alex March 24, 2012 at 5:18 pm

Hi Souphia,
Im not sure I understand you question.
What do you mean when you say “Copy it to your installation folder”?
why would you copy it? when the app starts up, the DB is retreived from isolated storage if it exists and if not, we create it.
does this make sense or am I not understanding your question?
Thanks
Alex

souphia March 25, 2012 at 4:19 am

when i created the same application like ToDoItems like msdn tutorial http://msdn.microsoft.com/en-us/library/hh202876(v=vs.92).aspx i could insert data and it shows me in the same run, but if i closed the application and i ran again the previous inserted data was not exist, it means in isolate storage db works like buffer, that’s why i couldn’t see the inserted data in second run.. so i extracted db from isolate storage with this http://msdn.microsoft.com/en-us/library/hh286408(v=vs.92).aspx
isn’t that true? i must have a database that user can insert data to it, not previous inserted and just user use data,”user must update db” ,
please tell me the truth of local db :-(

Alex March 25, 2012 at 6:57 am

The task app that you created from the MSDN docs should persist data across application launches. Can you email me a zip of the project file and I can have a look.
send it to aritzcovan@gmail.com

Leave a Comment

Previous post:

Next post: