Last week, we set up:

  • our machine for Windows Phone 8 development
  • an account and new project on tfs.visualstudio.com for our ALM needs (source control and work item tracking for now)

This week it’s time to create our first sprint and actually get some work done.

Backlog, sprints, and a bit of planning

In an attempt to clear my brain a bit, I threw all the ideas I had into the product backlog, created some sprints, and then picked a few items for the first sprint.

This is all quite easy to do from your Project Overview screen (https://[you].visualstudio.com/DefaultCollection/[YourProject].

image

Or you can just use the “New” item directly from the backlog itself.

initial-backlog

I also set the dates for the first several sprints. Note the start and end date for v1!

imageimage

Data models

I am a huge fan of small iterations and continuous improvement. HUGE fan. So for this sprint, all we're going to worry about storing for our items are:

  • Name
  • Price (freeform field)
  • Location
  • GPS (if available)
  • Date

I bet most of you have already normalized this into two tables. We’re going to use the built-in LINQ2SQL framework to store our data. Although I have zero familiarity with Entity Framework and I could use SQLite, I’m curious to see what problems exist using the “default” data storage mechanism.

(My first sprint ended up having zero code written. I’m finishing this post on Nov 3rd, already on day one of sprint two.)

Item

Let’s start with the basics for an item: id and name.

[Table]
public class Item : INotifyPropertyChanged, 
                    INotifyPropertyChanging
{
  private int id;
  private string name;

  public event PropertyChangingEventHandler PropertyChanging;
  public event PropertyChangedEventHandler PropertyChanged;

  [Column(IsPrimaryKey = true, IsDbGenerated = true, 
          DbType = "INT NOT NULL Identity",
          CanBeNull = false, AutoSync = AutoSync.OnInsert)]
   public int Id
   {
    get { return id; }
    set
    {
        if (id != value)
        {
            NotifyPropertyChanging("Id");
            id = value;
            NotifyPropertyChanged("Id");
        }
    }
   }

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

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

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

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

A table is defined as a class with a [Table] attribute, and columns are properties with a [Column] attribute. You can see there are some extra attribute properties for defining columns (e.g. primary, non-null, etc.). We also notify “the system” when property values change. If you’ve done work with XAML before this will all seem quite familiar.

One interesting addition is the version field: you can give a private column the IsVersion=true attribute property and “the system” will use this to speed up bulk updates.

Note for future: the boilerplate for defining a column and change notifications seems like it’s begging for T4. I suspect someone’s already done the work for us.

Price

Price has a few more attributes, but there’s nothing too interesting here yet. The NotifyPropertyChanging and NotifyPropertyChanged methods are going to be common for any data class so I’ve taken the opportunity to move them into a shareable base class.

[Table]
public class Price : NotifyPropertyChangedBase
{
  private int id;
  private DateTime date;
  private string amount;
  private string location;
  private string gps;

  [Column(IsPrimaryKey = true, IsDbGenerated = true, 
          DbType = "INT NOT NULL Identity",
          CanBeNull = false, AutoSync = AutoSync.OnInsert)]
  public int Id
  {
      get { return id; }
      set
      {
          if (id != value)
          {
              NotifyPropertyChanging("Id");
              id = value;
              NotifyPropertyChanged("Id");
          }
      }
  }

  [Column(CanBeNull = false)]
  public DateTime Date
  {
      get { return date; }
      set
      {
          if (date != value)
          {
              NotifyPropertyChanging("Date");
              date = value;
              NotifyPropertyChanged("Date");
          }
      }
  }

  [Column(CanBeNull = false)]
  public string Amount
  {
      get { return amount; }
      set
      {
          if (amount != value)
          {
              NotifyPropertyChanging("Price");
              amount = value;
              NotifyPropertyChanged("Price");
          }
      }
  }

  [Column]
  public string Location
  {
      get { return location; }
      set
      {
          if (location != value)
          {
              NotifyPropertyChanging("Location");
              location = value;
              NotifyPropertyChanged("Location");
          }
      }
  }

  [Column]
  public string Gps
  {
      get { return gps; }
      set
      {
          if (gps != value)
          {
              NotifyPropertyChanging("Gps");
              gps = value;
              NotifyPropertyChanged("Gps");
          }
      }
  }
}

Linking

So where are our entity relationships? Well, an Item can have many prices so let’s do that first.

private EntitySet prices;

public Item()
{
  prices = new EntitySet(
     (price) => { NotifyPropertyChanging("Prices"); 
                          price.Item = this; },
     (price) => { NotifyPropertyChanging("Prices"); 
                          price.Item = null; });
}

[Association(Storage = "prices", ThisKey = "Id", 
                      OtherKey = "itemId")]
public EntitySet Prices
{
  get { return prices; }
  set { prices.Assign(value); }
}

There are definitely some items to note here:

  • We map a one-to-many relationship using EntitySet<T>
  • The two delegates in the EntitySet constructor are called when a price is added to or removed from the Prices collection.
  • The Association attribute creates the foreign key relationship between Id and Price.itemId as well as defining a private column to hold the value (we’ve chosen to call it prices).

On the other side of our one-to-many relationship, we’ll add a few more fields and properties.

private EntityRef item;

[Column]
internal int itemId;

[Association(Storage = "item", ThisKey = "itemId", 
             OtherKey = "Id", IsForeignKey = true)]
public Item Item
{
   get { return item.Entity; }
   set
   {
       NotifyPropertyChanging("Item");

       item.Entity = value;
       if (value != null)
       {
           itemId = value.Id;
       }

       NotifyPropertyChanged("Item");
   }
}

Notes:

  • We use EntityRef instead of EntitySet (since there’s only one item on the other side of the relationship)
  • There is an internal field (itemId) separate from the Item. This was what was used in the foreign key association for Item.Prices

Does it work?

I honestly had no idea. So I wrote a test to find out. Adding a new project is quite simple (Add New Project > Windows Phone > Windows Phone Unit Test App) and once you add a solution reference to the main project, add a test like this:

[TestMethod]
public void CanCreateItemAndPrice()
{
   var dc = new WorthItDataContext("isostore:/worth.sdf");
   dc.CreateDatabase();

   var item = new Item() { Name = "Coconut Flour" };
   var price = new Price()
   {
       Date = new DateTime(2013, 10, 13, 10, 42, 0),
       Amount = "8.26/kg",
       Location = "Bulk Barn",
       Gps = "45.4582901, -75.4928055"
   };
   item.Prices.Add(price);
   dc.Items.InsertOnSubmit(item);
   dc.SubmitChanges();

   var sut = new WorthItDataContext("isostore:/worth.sdf");
   var coconutFlour = sut.Items.First();
   Assert.AreEqual(item.Name, coconutFlour.Name);
   var bulkBarnPrice = coconutFlour.Prices.First();
   Assert.AreEqual(price.Date, bulkBarnPrice.Date);
   Assert.AreEqual(price.Amount, bulkBarnPrice.Amount);
   Assert.AreEqual(price.Location, bulkBarnPrice.Location);
   Assert.AreEqual(price.Gps, bulkBarnPrice.Gps);
}

Looks like we're ok! Definitely running a bit behind for the first sprint; our retrospective will point out that the coder needs to spend more time developing. In the next post we'll connect our data store to a UI.

Interested in learning more about developing for Windows Phone 8? Enter your information to receive e-mail notifications on relevant articles.

Further Reading