Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use ETag to throw exception on insert if ETag doesn't match (except when it's *)

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.");
    }
}
like image 621
David S. Avatar asked Jul 05 '13 11:07

David S.


1 Answers

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.

like image 140
David S. Avatar answered Oct 20 '22 00:10

David S.