public override Task <IList <TableResult> > ExecuteMirrorBatchAsync( TableBatchOperation originalBatch, IList <TableResult> originalResponse, TableRequestOptions requestOptions = null, OperationContext operationContext = null) { ChainTableUtils.GetBatchPartitionKey(originalBatch); // For validation; result ignored // Copy the table. Entities are aliased to the original table, so don't mutate them. var tmpTable = new SortedDictionary <PrimaryKey, DynamicTableEntity>(table); int tmpNextEtag = nextEtag; var results = new List <TableResult>(); for (int i = 0; i < originalBatch.Count; i++) { TableOperation op = originalBatch[i]; TableOperationType opType = op.GetOperationType(); ITableEntity passedEntity = op.GetEntity(); PrimaryKey key = passedEntity.GetPrimaryKey(); DynamicTableEntity oldEntity = tmpTable.GetValueOrDefault(key); DynamicTableEntity newEntity = null; HttpStatusCode statusCode = HttpStatusCode.NoContent; if (opType == TableOperationType.Insert) { if (oldEntity != null) { throw ChainTableUtils.GenerateBatchException(HttpStatusCode.Conflict, i); } else { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity); statusCode = HttpStatusCode.Created; } } else if (opType == TableOperationType.InsertOrReplace) { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity); } else if (opType == TableOperationType.InsertOrMerge) { if (oldEntity == null) { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity); } else { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(oldEntity); Merge(newEntity, passedEntity); } } else if (opType == TableOperationType.Delete && passedEntity.ETag == ChainTable2Constants.ETAG_DELETE_IF_EXISTS) { tmpTable.Remove(key); } else if (oldEntity == null) { throw ChainTableUtils.GenerateBatchException(HttpStatusCode.NotFound, i); } else if (string.IsNullOrEmpty(passedEntity.ETag)) { // Enforce this because real Azure table will. // XXX Ideally do this up front. throw new ArgumentException(string.Format("Operation {0} requires an explicit ETag.", i)); } else if (passedEntity.ETag != ChainTable2Constants.ETAG_ANY && oldEntity.ETag != passedEntity.ETag) { throw ChainTableUtils.GenerateBatchException(HttpStatusCode.PreconditionFailed, i); } else if (opType == TableOperationType.Delete) { tmpTable.Remove(key); } else if (opType == TableOperationType.Replace) { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity); } else if (opType == TableOperationType.Merge) { newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(oldEntity); Merge(newEntity, passedEntity); } else { // IChainTable2 does not allow Retrieve in a batch. throw new NotImplementedException(); } if (newEntity != null) { newEntity.ETag = (originalResponse != null) ? originalResponse[i].Etag : (tmpNextEtag++).ToString(); newEntity.Timestamp = DateTimeOffset.MinValue; // Arbitrary, deterministic tmpTable[key] = newEntity; } results.Add(new TableResult { Result = passedEntity, HttpStatusCode = (int)statusCode, Etag = (newEntity != null) ? newEntity.ETag : null, }); } // If we got here, commit. table = tmpTable; nextEtag = tmpNextEtag; for (int i = 0; i < originalBatch.Count; i++) { if (results[i].Etag != null) // not delete { originalBatch[i].GetEntity().ETag = results[i].Etag; } } return(Task.FromResult((IList <TableResult>)results)); }