Azure Table Storage Associations with RIA Services

For this post, I’ll assume you read my intro to Windows Azure Table Storage. If not, take a second to read it. This time around I’m skipping past the contextualizing.

In my first entry, I showed how to get a simple entity all the way from Table Storage to Silverlight. It was a necessary step, but you’ll need to know more to write an application. This time around, I want to walk through creating two related tables; one with a reference to the other. All the code here will be things you need to write for your Web Role project. This sample follows a three-step pattern you’ll find yourself using over and over as you write WCF RIA Services applications that use Windows Azure Table Storage.

Step 1: Define your Entities

We’ll stick to a couple simple entities with a parent-child relationship where the parent has a collection of children.

   public class MyParentEntity : TableEntity
  {
    public MyParentEntity()
    {
      this.Children = new List<MyChildEntity>();
    }

    public string MyParentName { get; set; }

    [Include]
    [Association("Parent_Child", "PartitionKey,RowKey",
      "ParentPartitionKey,ParentRowKey")]
    public List<MyChildEntity> Children { get; set; }
  }

  public class MyChildEntity : TableEntity
  {
    public string MyChildName { get; set; }

    [Association("Parent_Child", "ParentPartitionKey,ParentRowKey",
      "PartitionKey,RowKey", IsForeignKey = true)]
    public MyParentEntity Parent { get; set; }
    public string ParentPartitionKey { get; set; }
    public string ParentRowKey { get; set; }
  }

In case you aren’t familiar with all the ins-and-outs of entity modeling, I’ll run down the details. As a point of reminder, all table storage entities are keyed by the PartitionKey and RowKey properties defined in the TableEntity base class.

MyParentEntity has a collection of MyChildEntity entities with two attributes; Include and Association. The IncludeAttribute tells RIA Services that the collection of children can be included in serialization. It may feel like a redundant step, but it protects you from sending more data than you intend to. The AssociationAttribute defines how the parent and child entities are related. On the parent, it is created using (1) the association name, (2) the key properties on the parent, and (3) the foreign-key properties on the child that point to the key properties on the parent.

MyChildEntity has three properties that it uses to reference the parent. The Parent property contains the mirror image of the AssociationAttribute found in MyParentEntity. This time the association is created using (1) the association name, (2) the foreign-key properties on the child that point to the key properties on the parent, (3) the key properties on the parent, and (4) setting the IsForeignKey property to true to identify the child entity as the one with the foreign-key properties.

One important point to highlight with entity modeling is we can successfully use property types that are not supported by table storage. For example, since List<MyChildEntity> on MyParentEntity is not a supported type, the serializer will only send the PartitionKey, RowKey, Timestamp, and MyParentName to the database. The important inverse of this is that because the list is not stored in the database we will have to re-populate it when sending our parent entities to the client (more on that later).

Step 2: Define your EntityContext

This second step is fairly easy. We’ll need to create an entity context to use in our domain service. In this case we expose two properties; one for the parents and one for the children.

   public class MyEntityContext : TableEntityContext
  {
    public MyEntityContext() : 
      base(RoleEnvironment.
             GetConfigurationSettingValue("DataConnectionString"))
    {
    }

    public TableEntitySet<MyParentEntity> MyParentEntities
    {
      get { return base.GetEntitySet<MyParentEntity>(); }
    }

    public TableEntitySet<MyChildEntity> MyChildEntities
    {
      get { return base.GetEntitySet<MyChildEntity>(); }
    }
  }

Step 3: Define your DomainService

In this last step we’ll create a domain service and add some logic to read and write to our tables.

   [EnableClientAccess()]
  public class MyDomainService : TableDomainService<MyEntityContext>
  {
    public IQueryable<MyParentEntity> GetMyParentEntities()
    {
      return this.EntityContext.MyParentEntities;
    }

    public IEnumerable<MyParentEntity> GetMyParentEntitiesWithChildren()
    {
      IEnumerable<MyParentEntity> parents =
        this.EntityContext.MyParentEntities;
      IEnumerable<MyChildEntity> children =
        this.EntityContext.MyChildEntities;

      // Hook the association properties together
      foreach (MyParentEntity parent in parents)
      {
        foreach (MyChildEntity child in children)
        {
          if ((parent.PartitionKey == child.ParentPartitionKey) &&
              (parent.RowKey == child.ParentRowKey))
          {
            parent.Children.Add(child);
            child.Parent = parent;
          }
        }
      }

      return parents;
    }

    public void AddMyParentEntity(MyParentEntity entity)
    {
      this.EntityContext.MyParentEntities.Add(entity);

      // Make sure the child entities get the updated keys
      foreach (MyChildEntity child in entity.Children)
      {
        child.ParentPartitionKey = entity.PartitionKey;
        child.ParentRowKey = entity.RowKey;
      }
    }

    public void DeleteMyParentEntity(MyParentEntity entity)
    {
      this.EntityContext.MyParentEntities.Delete(entity);

      // Delete the child entities
      IEnumerable<MyChildEntity> children =
        this.EntityContext.MyChildEntities.Where(c =>
          (c.ParentPartitionKey == entity.PartitionKey) &&
          (c.ParentRowKey == entity.RowKey));

      foreach (MyChildEntity child in children)
      {
        this.EntityContext.MyChildEntities.Delete(child);
      }
    }

    public void UpdateMyParentEntity(MyParentEntity entity)
    {
      this.EntityContext.MyParentEntities.Update(entity);
    }

    public IQueryable<MyChildEntity> GetMyChildEntities()
    {
      return this.EntityContext.MyChildEntities;
    }

    public void AddMyChildEntity(MyChildEntity entity)
    {
      this.EntityContext.MyChildEntities.Add(entity);
    }

    public void DeleteMyChildEntity(MyChildEntity entity)
    {
      this.EntityContext.MyChildEntities.Delete(entity);
    }

    public void UpdateMyChildEntity(MyChildEntity entity)
    {
      this.EntityContext.MyChildEntities.Update(entity);
    }
  }

Starting with the simplest part, with this association we were able to use all the standard methods for reading, adding, updating, and deleting MyChildEntity. Also, updating MyParentEntity didn’t require any additional logic.

In GetMyParentEntitiesWithChildren, we’re filling in the relationship properties (like I alluded to earlier). We’re looking for parents and children where the association properties match and tying them together. After adding the parent to the entity set in AddMyParentEntity, we’re iterating through all the children we’re adding as part of the same change set and updating their foreign-key properties. Finally, in DeleteMyParentEntity we’re looking up all the children associated with the parent and deleting them.

In this post I’ve shown the basics for creating related tables. Hopefully the samples highlight the kind of code required to work with table storage and gets you on your way to writing a cloud app.