Try fast search NHibernate

08 May 2010

Azure: TableServiceEntity or TableDataRow


There are a lot of things to say about Windows Azure SDK, and how many classes I would remove, especially for those to manage the TableStorage. In the project I’m involved there is no time, so far, to implement what we would like to have… the main issue is that the SDK has some limitation that the Windows Azure Platform does not have.
Today is the day of the base class TableServiceEntity.
The first issue is its name. If you want to confuse a user about what is represented in a Azure’s table the best way is put the postfix “Entity”.
The implementation of the base class proposed in the SDK is this:
  1. public abstract class TableServiceEntity
  2. {
  3.     protected TableServiceEntity()
  4.     {
  5.     }
  6.  
  7.     protected TableServiceEntity(string partitionKey, string rowKey)
  8.     {
  9.         this.PartitionKey = partitionKey;
  10.         this.RowKey = rowKey;
  11.     }
  12.  
  13.     public virtual string PartitionKey { get; set; }
  14.     public virtual string RowKey { get; set; }
  15.     public DateTime Timestamp { get; set; }
  16. }
Nice POCO, no ? The problem with it is that it is too much POCO (the translation of the word ‘poco’ from Italian and Spanish is: ‘FEW’).

Problem 1

The class TableServiceEntity is only a mere base classes with three properties required by Azure’s Table Storage and I can’t call it “Entity” because in MY DOMAIN does not exist a string property called ‘PartitionKey’, does not exist a string property called ‘RowKey’, does not exist “Timestamp” and overall, in my case, I’ll not use the Azure’s table-storage to store the state of MY real entities.

Problem 2

Since PartitionKey and RowKey represents the composite primary key of a data-row, and both are required, which is the class with the responsibility to return valid values composed with some of MY properties ?
For me the answer is clear: the same instance should provide valid values of PartitionKey and RowKey and both properties shouldn’t have a public setter.
Since the property Timestamp is completely managed by the Table-Storage, should the property have a public setter ? … No, it shouldn’t.
Unfortunately the Azure SDK does not allow neither private nor protected setters!! :((

Problem 3

As said PartitionKey and RowKey are required and I would have values, needed to compose it, required in the constructor. For example I would have something like this:
  1. public NewsInfoPerCategoryData(string category, string title, DateTime createdAt)
I don't want another parameter-less public constructor. If needed (and believe me I know which is the reason) I can put there a protected parameter-less constructor. Well… to be short, if you use the SDK forget it!! you must have a public parameter less constructor. :((
To create a new instance of a class with a protected parameter-less constructor the C# line is:
  1. var instance = Activator.CreateInstance(typeof (TTableEntity), true);
When you have ten minutes, please, use it to create an instance as result of a query, thanks.

Problem 4

The other problem is the Timestamp. So far I have not checked its behavior directly in the cloud but in the Development Fabric it seems to have the same precision problem of the DateTime of MsSQL2005: it is rounded to 3 millisecond (hopefully it is only a limitation of Development Fabric because if the limitation is the same in the cloud we will have a real PITA to manage the optimistic-locking, or we must hope in the help of fate).

Why TableServiceEntity is too much POCO ?

If you will use the SDK you will use classes inherited from TableServiceEntity with the TableServiceContext. The TableServiceContext is a state-full-context based on identity-hash-table (why we should use a state-full-context to work with a REST-FULL service is one of those “mysteries of Faith”).
Inside the TableServiceContext, two instances represent the same entity if they have the same Hash-Code but the implementation of TableServiceEntity does not care about this fact… in practice the implementation should reflect what will happen in the storage where two “entities” will represent the same state if they have the same Type (or we can say same table), the same PartitionKey and the same RowKey.

My proposal

Well… not exactly my real proposal… this is more, the proposal “accepting” some of above constraints.
The code below in under Ms-PL (if you need another license, to be comfortable, let me know).
  1. [DataServiceKey(new[] { "PartitionKey", "RowKey" })]
  2. [CLSCompliant(false)]
  3. public abstract class TableDataRow
  4. {
  5.     private int? requestedHashCode;
  6.     private string partitionKey;
  7.     private string rowKey;
  8.  
  9.     public string PartitionKey
  10.     {
  11.         get { return partitionKey ?? (partitionKey = CreatePartitionKey()); }
  12.         set { partitionKey = value; }
  13.     }
  14.  
  15.     public string RowKey
  16.     {
  17.         get { return rowKey ?? (rowKey = CreateRowKey()); }
  18.         set { rowKey = value; }
  19.     }
  20.  
  21.     public DateTime Timestamp { get; set; }
  22.  
  23.     protected abstract string CreatePartitionKey();
  24.     protected abstract string CreateRowKey();
  25.  
  26.     public override bool Equals(object obj)
  27.     {
  28.         return Equals(obj as TableDataRow);
  29.     }
  30.  
  31.     public bool Equals(TableDataRow other)
  32.     {
  33.         if (ReferenceEquals(null, other))
  34.         {
  35.             return false;
  36.         }
  37.         if (ReferenceEquals(this, other))
  38.         {
  39.             return true;
  40.         }
  41.         return GetType().IsAssignableFrom(other.GetType()) && Equals(other.PartitionKey, PartitionKey) &&
  42.                      Equals(other.RowKey, RowKey);
  43.     }
  44.  
  45.     public override int GetHashCode()
  46.     {
  47.         if (!requestedHashCode.HasValue)
  48.         {
  49.             unchecked
  50.             {
  51.                 requestedHashCode = (GetType().GetHashCode() * 397) ^ (PartitionKey != null ? PartitionKey.GetHashCode() : 0) ^
  52.                                                         (RowKey != null ? RowKey.GetHashCode() : 0);
  53.             }
  54.         }
  55.         return requestedHashCode.Value;
  56.     }
  57. }

3 comments:

  1. Great post. This is certainly a vast improvement over TableServiceEntity.

    ReplyDelete
  2. @Colin
    I know, but I can play with the meaning of the word in Spanish and Italian.

    ReplyDelete