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.
Comments
Anonymous
January 06, 2011
There is a template to generate this class from edmx file?Anonymous
January 06, 2011
No, there aren't any templates. If you're interested in moving an existing database or schema to the cloud, SQL Azure is typically the better choice. There are plenty of tools to help migrate a database from a local instance to the cloud. Also, updating a RIA app to move from a local to a cloud database is as easy as changing the connection string. Everything else works as before.Anonymous
March 16, 2011
The comment has been removedAnonymous
March 16, 2011
@Nick That means the RIA code generator doesn't like your type. Just add the [Exclude] attribute to the property.Anonymous
March 18, 2011
Thanks for the prompt answer Kyle - it worked perfectly. I have one more question: how would I go about applying a Filter string as in msdn.microsoft.com/.../ff683669.aspx to get those entities with RowKeys in between two values? I don't think I can use this.EntityContext.MyEntityName.Where to achieve this, can I?Anonymous
March 18, 2011
I guess I spoke too soon. While [Exclude] certainly lets it build, it also excludes it from the automatically generated domain service metadata, so I can't access the Properties property client-side. It seems that we can only use some unsupported types (like your List<>), but Dictionary<> isn't one of them. Is this correct or is there any way to add Properties back in to the metadata client side?Anonymous
March 18, 2011
RIA doesn't support collections of objects. You can use the approach with the Windows Azure backend, but you'll have to put the values in concrete properties to send it back to the client. Alternatively, RIA supports collections of strings so Dictionary<string, string> would work.Anonymous
January 30, 2014
The table domainservice does not showing in web.g.cs. Am I missing something?