Question

I need to be able to insert an entity to an azure storage table under these conditions:

  • if it doesn't exist, insert.
  • if it exists, but I specify ETag to be *, then replace.
  • if it exists, but ETag has another value, then throw StorageException with code 409 or 412. (for example I would try to insert an entity I have retrieved, but it has been updated from elsewhere in the meantime)

I made this simple program to test, but I can't figure out how to get this to work. it never reaches the exception. (I thought this was regular ETag behavior requirements).

Note that if I use Insert operation instead of InsertOrReplace I get an exception even if ETag has an unchanged value.

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
tableClient.RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(3), 10);
var testtable = tableClient.GetTableReference("davidstesttable");
testtable.CreateIfNotExists();

//insert first entity
var newentity = new DynamicTableEntity("idunno", String.Empty, "*", new Dictionary<string, EntityProperty> { { "testprop", new EntityProperty("testval") } });
Msg("insert initial entity");
testtable.Execute(TableOperation.InsertOrReplace(newentity));
Msg("inserted");

Msg("retrieving");
TableResult tableResult = testtable.Execute(TableOperation.Retrieve("idunno", String.Empty));
DynamicTableEntity firstRetrieve = (DynamicTableEntity)tableResult.Result;
Msg("retrieved. etag: " + firstRetrieve.ETag);

Msg("inserting the initial entity again to change the ETag in the table");
testtable.Execute(TableOperation.InsertOrReplace(newentity));
Msg("inserted");

Msg("retrieving");
TableResult tableResult2 = testtable.Execute(TableOperation.Retrieve("idunno", String.Empty));
DynamicTableEntity secondRetrieve = (DynamicTableEntity)tableResult2.Result;
Msg("retrieved. etag: " + secondRetrieve.ETag);

if(firstRetrieve.ETag != secondRetrieve.ETag)
{
    Msg("confirmed entity in table now has different etag");
    Msg("inserting the first retrieved. (etags should not match now, expecting StorageException)");
    try
    {
        //If I use Insert operation instead of InsertOrReplace, I do get the exception,
        //but I tested with this and then I get the exception even if the ETag is unchanged or * !
        testtable.Execute(TableOperation.InsertOrReplace(firstRetrieve));
        Msg("hmm should not have reached here!");
    }
    catch (StorageException e)
    {
        if(e.RequestInformation.HttpStatusCode == 409 || e.RequestInformation.HttpStatusCode == 412)
            Msg("got exception as expected because of the mismatching ETag.");
    }
}
Was it helpful?

Solution

I may have found a solution. Will accept this if nobody has a better answer.

I tried to add If-Match header of the OperationContext, with the Etag as the value. This worked. I thought this was an automatically added thing, but apparently not.

testtable.Execute(
  TableOperation.InsertOrReplace(firstRetrieve),
  null,
  new OperationContext {
    UserHeaders = new Dictionary<String, String>
                      {
                        { "If-Match", firstRetrieve.ETag }
                      }
  }
);

Now, when using null as ETag, I can InsertOrReplace, and it also properly checks ETag if it's something else.

Note that if I use * as ETag, I get a 404 exception if the entity doesn't exist. So use null to get the intended functionality. Or just detect * and don't add the header.

EDIT:

Caveat: If you'd want to insert a new item (ETag == null) but still want to get an exception code 409 conflict if it already exists, you must use the Insert operation instead of the InsertOrReplace operation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top