Dealing with TableStorage ETag with SDK >= 2.1

Damir Dobric Posts

Next talks:

 

    

Follow me on Twitter: #ddobric



 

 

Archives

Due many changes (mostly good changes) in table storage SDK-s, some existing examples might not work anymore.
I had many examples written in SDK 1.6, which unfortunately do not work with new SDK (2.1 and 2.2).
One of major mistakes which I figured out when working with table operations is caused by different projection strategies of ETag property.
For example, you might get typically following exception:

ArgumentException:

Replace requires an ETag (which may be the '*' wildcard).

This exception is usually caused when trying to create the instance of table operations like:

   TableOperation replaceOperation = TableOperation.Replace(existingItem);

   TableOperation mergeOperation = TableOperation.Merge(existingItem);

   TableOperation deleteOperation = TableOperation.Delete(existingItem);


You might be surprised that this does not work, because you have probably returned the instance of ‘existingItem’ just a step before.
The problem is in following. Take a look on existingItem.ETag property and you will see that it is empty.
Note that this property is necessarily set by all query operations.
For example following code show typical query sample, which returns a single user. The result is a single user, because we query by rowkey and partition key.
These two properties uniquely lookup a single entity instance.

           TableServiceContext ctx = new TableServiceContext
          (m_StorageAccount.CreateCloudTableClient());
 
           
var query = ctx.CreateQuery<AttendeeRecord>(cTableName);
 
           
var results = from g in query
                         
where g.PartitionKey == partitionKey &&
                          g.RowKey == rowKey
                         
select g; 

 
           
var resultArray = results.ToArray();


However you should know that sample above, will not project ETag property of the user. Interestingly ETag
is returned from service (see picture below-right), but it is not projected (picture below-left).

image image
If you use such entity instance as an input of Replace, Merge or Delete table operations it will throw!!! 

   TableOperation replaceOperation = TableOperation.Replace(resultArray[0]);

   TableOperation mergeOperation = TableOperation.Merge(resultArray[0]);

   TableOperation deleteOperation = TableOperation.Delete(resultArray[0]);


These operations require ETag, which must (should) be returned by storage service. Keep that in Mind.

How to Replace existing entity instance ?

Note in this example is used retrieve operation which by default project ETag property.

        /// <summary>
       
/// Demonstrates how to replace an entity. 
       
/// Note that retrieve operation returns entity
       
/// with ETAG.
        /// </summary>
       
/// <param name="partitionKey"></param>
       
/// <param name="rowKey"></param>
       
public void ReplaceSample(string partitionKey, string rowKey)
       
           
 
           
TableOperation retrieveOperation =
           
TableOperation.Retrieve<AttendeeRecord>(partitionKey, rowKey);
 
           
var existingItem = m_CloudTable.Execute(retrieveOperation);           
 
           
if (existingItem != null)
            {
               
TableOperation replaceOperation =
               
TableOperation.Replace(existingItem.Result as AttendeeRecord);
                m_CloudTable.Execute(replaceOperation);
            }
        }
 

 

Sometimes you will have simple query in some previous step, which possibly does not project ETag. Because the ETag is in this case null, you can set it to ‘*’:

        /// <summary>
       
/// Demonstrates how to Merge changes of an entity. 
       
/// Note that retrieve operation returns entity
       
/// with ETAG.
        /// </summary>
       
/// <param name="partitionKey"></param>
       
/// <param name="rowKey"></param>
       
public void ReplaceSample(string partitionKey, string rowKey)
       

           TableServiceContext ctx = new
          
TableServiceContext(m_StorageAccount.CreateCloudTableClient());
 
           
var query = ctx.CreateQuery<AttendeeRecord>(cTableName);
 
           
var results = from g in query
                         
where g.PartitionKey == partitionKey &&
                          g.RowKey == rowKey
                         
select g;
 
           
var i = results.FirstOrDefault();
           
i.ETag = "*";
           
TableOperation replaceOperation = TableOperation.Replace(i);

            m_CloudTable.Execute(replaceOperation );

           
        } 

The approach from last sample (ETag = ‘*’) can also be used by Merge and Delete table operations. But, you should know
that replacing ETag prevents build in optimistic concurrency mechanism.

How to Merge changes on an existing entity instance ?

This operation requires ETag too.

        /// <summary>
       
/// Demonstrates how to Merge changes of an entity. 
       
/// Note that retrieve operation returns entity
       
/// with ETAG.
        /// </summary>
       
/// <param name="partitionKey"></param>
       
/// <param name="rowKey"></param>
       
public void ReplaceSample(string partitionKey, string rowKey)
       

             TableOperation retrieveOperation =
             TableOperation.Retrieve<AttendeeRecord>(partitionKey, rowKey);
 
            
var existingItem = m_CloudTable.Execute(retrieveOperation);
           
            
if (existingItem != null)
             {
                ((
AttendeeRecord)existingItem.Result).FirstName += "+";
               
TableOperation mergeOperation =
               
TableOperation.Merge(existingItem.Result as AttendeeRecord);
                m_CloudTable.Execute(mergeOperation);
            }
 
        } 


How to delete existing entity instance?

Also this operation requires ETag.

        /// <summary>
       
/// Demonstrates how to Merge changes of an entity. 
       
/// Note that retrieve operation returns entity
       
/// with ETAG.
        /// </summary>
       
/// <param name="partitionKey"></param>
       
/// <param name="rowKey"></param>
       
public void ReplaceSample(string partitionKey, string rowKey)
       

           TableServiceContext ctx = new
          
TableServiceContext(m_StorageAccount.CreateCloudTableClient());
 
           
var query = ctx.CreateQuery<AttendeeRecord>(cTableName);
 
           
var results = from g in query
                         
where g.PartitionKey == partitionKey &&
                          g.RowKey == rowKey
                         
select g; 
 
          

           foreach (var rec in results){

           
rec.ETag = "*";
           
TableOperation delOp = TableOperation.Delete(i); 
            m_CloudTable.Execute(delOp);

        } 


Query which does not project ETag ?

Following example shows how to use lynq query expressions with Table Context.
Unfortunately ETag is now not automatically projected.

        /// <summary>
       
/// Demonstrate the query operation which does not  
       
/// project ETag.
       
/// Entities retrieved by this query CAN NOT be used in Merge, Replace and Delete operations.
       
/// </summary>
       
/// <returns></returns>
       
public IEnumerable<AttendeeRecord> SelectAll()
        {
           
TableServiceContext ctx = 
           
new TableServiceContext(m_StorageAccount.CreateCloudTableClient());
 
           
var query = ctx.CreateQuery<AttendeeRecord>(cTableName);
 
           
var results = from g in query
                         
where g.PartitionKey != ""
                         
select g;
 
           
return results;
        }


 

Query which does project ETag automatically?

Following example shows how to use lynq query expressions with Table Context.
Unfortunately ETag is now not automatically projected.

        /// <summary>
       
/// Demonstrate how to use query which automatically projects ETag.
       
/// Entities retrieved by this query can be used in Merge, Replace and Delete operations.
       
/// </summary>
       
/// <returns></returns>
       
public IEnumerable<AttendeeRecord> SelectAll2()
        {
           
TableQuery<AttendeeRecord> query =
               
new TableQuery<AttendeeRecord>().Where(
               
TableQuery.CombineFilters(
                   
TableQuery.GenerateFilterCondition("PartitionKey",
                   
QueryComparisons.NotEqual, "-1"),
                   
TableOperators.And,
                   
TableQuery.GenerateFilterCondition("RowKey",
               
QueryComparisons.NotEqual, "-1")));
 
           
var results = m_CloudTable.ExecuteQuery(query);
 
           
return results;          
        }


  

Custom Resolver

Sometimes you want to style returned entity instance. Usually .NET framework calls such strategies custom serializer.
Suddenly in a case of Table Storage SDK this is called custom resolver. Following example show how you can inject your code
in deserialization process. When the loop below traverses through records the resolver’s delegate will be invoked.
At this place you have a chase to manipulate the instance as you like.
Personally I miss here a possibility to get the instance deserialized by default serializer.

        /// <summary>
       
/// Demonstrates using of custom resolver.
       
/// </summary>
       
/// <returns></returns>
       
public IEnumerable<AttendeeRecord> SelectAll3()
        {
           
EntityResolver<AttendeeRecord> resolver =
           
new EntityResolver<AttendeeRecord>((partKey, rowKey, timestamp, props, etag) =>
                {
                   
AttendeeRecord rec = new AttendeeRecord();
                    rec.ETag = etag;
                    rec.FirstName = props[
"FirstName"].StringValue;
                   
//todo..
                   
return rec;
                }
            );
 
           
TableQuery<AttendeeRecord> query =
           
new TableQuery<AttendeeRecord>().Where(
                       
TableQuery.CombineFilters(
                           
TableQuery.GenerateFilterCondition("PartitionKey",
                             QueryComparisons.NotEqual, "-1"),
                           
TableOperators.And,
                           
TableQuery.GenerateFilterCondition("RowKey",
                            
QueryComparisons.NotEqual, "-1")));
 
           
var results = m_CloudTable.ExecuteQuery(query, resolver);
 
           
return results;
        }


      

    void Main()
    {

       var res = ds.SelectAll3();
       foreach (var rec in res)
         . . . ;
      }


Posted Oct 29 2013, 06:34 AM by Damir Dobric
Filed under: ,
developers.de is a .Net Community Blog powered by daenet GmbH.