/// <summary>
        /// Get increment and decrement operation for count table
        /// </summary>
        /// <param name="operation">Store operation</param>
        /// <param name="value">Value to add to counter</param>
        /// <returns>Table operation</returns>
        private async Task <TableOperation> GetCountTableOperation(Operation operation, double value)
        {
            string partitionKey = this.GetPartitionKey(operation);
            string rowKey       = this.GetRowKey(operation);

            CountTable  countTable = operation.Table as CountTable;
            CountEntity entity     = await this.QueryCountAsync(countTable, operation.PartitionKey, operation.Key);

            TableOperation tableOperation = null;

            if (entity != null)
            {
                entity.Count += value;

                // The following will generate a precondition failed exception if entity was updated
                // between the read and replace operation.
                Operation replaceOperation = Operation.Replace(countTable, operation.PartitionKey, operation.Key, entity);
                tableOperation = await this.GetTableOperation(replaceOperation);

                return(tableOperation);
            }

            if (operation.OperationType == OperationType.InsertOrIncrement)
            {
                // In case an entity gets added between the read and the following operation, it will generate
                // a conflict exception.
                Operation insertOperation = Operation.Insert(countTable, operation.PartitionKey, operation.Key, value);
                tableOperation = await this.GetTableOperation(insertOperation);

                return(tableOperation);
            }
            else
            {
                CountEntity replaceEntity = new CountEntity()
                {
                    Count = value,
                    ETag  = "W/\"datetime'2000-01-01T00%3A00%3A00.0000000Z'\"" // Dummy ETag Value
                };

                // We want to throw a not found exception. The following will throw a not found exception.
                // In case an entity gets added between the read and the following operation,
                // it will generate a precondition failed exception.
                // TODO: Need to make this consistent with cache.
                Operation replaceOperation = Operation.Replace(countTable, operation.PartitionKey, operation.Key, replaceEntity);
                tableOperation = await this.GetTableOperation(replaceOperation);

                return(tableOperation);
            }
        }
        /// <summary>
        /// Get rollback operation for store operation.
        /// The rollback operation typically deletes the invalid cache entity
        /// </summary>
        /// <param name="preOperation">Pre operation</param>
        /// <param name="preResult">Result of pre operation</param>
        /// <returns>Rollback operation</returns>
        private Operation GetRollbackOperation(Operation preOperation, Result preResult)
        {
            Table     table             = preOperation.Table;
            Operation rollbackOperation = null;

            if (table is ObjectTable)
            {
                ObjectEntity deleteEntity = new ObjectEntity();
                deleteEntity.ETag = preResult.ETag;
                rollbackOperation = Operation.Delete(preOperation.Table as ObjectTable, preOperation.PartitionKey, preOperation.Key, deleteEntity);
            }
            else if (table is FixedObjectTable)
            {
                ObjectEntity deleteEntity = new ObjectEntity();
                deleteEntity.ETag = preResult.ETag;
                rollbackOperation = Operation.Delete(preOperation.Table as FixedObjectTable, preOperation.PartitionKey, preOperation.Key, deleteEntity);
            }
            else if (table is FeedTable)
            {
                if (preResult.EntitiesAffected == 0)
                {
                    return(null);
                }

                // To rollback, we cannot delete the invalidated item in a feed.
                // This will violate the cache feed invariant.
                // The best we can do is to expire the item right away so that the next read can correct the cache.
                // Not the negative sign in the parameter.
                FeedEntity invalidEntity = this.GenerateInvalidCacheEntity <FeedEntity>(-this.config.CacheExpiryInSeconds);
                invalidEntity.ETag = preResult.ETag;
                rollbackOperation  = Operation.Replace(preOperation.Table as FeedTable, preOperation.PartitionKey, preOperation.Key, preOperation.ItemKey, invalidEntity);
            }
            else if (table is CountTable)
            {
                CountEntity deleteEntity = new CountEntity();
                deleteEntity.ETag = preResult.ETag;
                rollbackOperation = Operation.Delete(preOperation.Table as CountTable, preOperation.PartitionKey, preOperation.Key, deleteEntity);
            }

            if (rollbackOperation != null)
            {
                this.SetupCacheEntityETag(rollbackOperation);
            }

            return(rollbackOperation);
        }
        /// <summary>
        /// Get count caching operation
        /// </summary>
        /// <param name="table">Store table</param>
        /// <param name="partitionKey">Partition key</param>
        /// <param name="key">Key for object or feed</param>
        /// <param name="cacheEntity">Cache entity</param>
        /// <param name="persistentEntity">Persistent store entity</param>
        /// <returns>Caching operation</returns>
        private Operation GetCountCachingOperation(Table table, string partitionKey, string key, Entity cacheEntity, Entity persistentEntity)
        {
            Operation cachingOperation = null;

            if (cacheEntity != null && this.IsCacheEntityExpired(cacheEntity))
            {
                if (persistentEntity != null)
                {
                    if (table is CountTable)
                    {
                        cachingOperation                   = Operation.Replace(table as CountTable, partitionKey, key, persistentEntity as CountEntity);
                        cachingOperation.Entity.ETag       = cacheEntity.ETag;
                        cachingOperation.Entity.CustomETag = persistentEntity.ETag;
                    }
                }
                else
                {
                    if (table is CountTable)
                    {
                        CountEntity deleteEntity = new CountEntity();
                        deleteEntity.ETag = cacheEntity.ETag;
                        cachingOperation  = Operation.Delete(table as CountTable, partitionKey, key, deleteEntity);
                    }
                }
            }
            else if (cacheEntity == null)
            {
                if (persistentEntity != null)
                {
                    if (table is CountTable)
                    {
                        cachingOperation = Operation.Insert(table as CountTable, partitionKey, key, (persistentEntity as CountEntity).Count);
                        cachingOperation.Entity.CustomETag = persistentEntity.ETag;
                    }
                }
            }

            return(cachingOperation);
        }
        /// <summary>
        /// Get post operation for a store operation in default mode.
        /// The post operation typically makes the cache consistent with the store.
        /// There are exceptions: For merge operations, we simple delete the cache entry
        /// A read operation on the whole item should bring it back to the cache.
        /// </summary>
        /// <param name="operation">Store operation</param>
        /// <param name="preResult">Result of pre operation</param>
        /// <param name="result">Result of operations</param>
        /// <returns>Post operation</returns>
        private Operation GetPostOperation(Operation operation, Result preResult, Result result)
        {
            Table         table         = operation.Table;
            OperationType operationType = operation.OperationType;
            Entity        entity        = operation.Entity;
            Operation     postOperation = null;

            if (table is ObjectTable)
            {
                switch (operationType)
                {
                case OperationType.Insert:
                    postOperation = Operation.Insert(table as ObjectTable, operation.PartitionKey, operation.Key, entity as ObjectEntity);
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Replace:
                case OperationType.InsertOrReplace:
                    postOperation                   = Operation.Replace(table as ObjectTable, operation.PartitionKey, operation.Key, entity as ObjectEntity);
                    postOperation.Entity.ETag       = preResult.ETag;
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Delete:
                case OperationType.DeleteIfExists:
                case OperationType.Merge:
                case OperationType.InsertOrMerge:
                    ObjectEntity deleteEntity = new ObjectEntity();
                    deleteEntity.ETag = preResult.ETag;
                    return(Operation.Delete(table as ObjectTable, operation.PartitionKey, operation.Key, deleteEntity));
                }
            }
            else if (table is FixedObjectTable)
            {
                switch (operationType)
                {
                case OperationType.Insert:
                    postOperation = Operation.Insert(table as FixedObjectTable, operation.PartitionKey, operation.Key, entity as ObjectEntity);
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Replace:
                case OperationType.InsertOrReplace:
                    postOperation                   = Operation.Replace(table as FixedObjectTable, operation.PartitionKey, operation.Key, entity as ObjectEntity);
                    postOperation.Entity.ETag       = preResult.ETag;
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Delete:
                case OperationType.DeleteIfExists:
                    ObjectEntity deleteEntity = new ObjectEntity();
                    deleteEntity.ETag = preResult.ETag;
                    return(Operation.Delete(table as FixedObjectTable, operation.PartitionKey, operation.Key, deleteEntity));
                }
            }
            else if (table is FeedTable)
            {
                if (preResult.EntitiesAffected == 0)
                {
                    return(null);
                }

                switch (operationType)
                {
                case OperationType.Insert:
                case OperationType.Replace:
                case OperationType.InsertOrReplace:
                    postOperation                   = Operation.Replace(table as FeedTable, operation.PartitionKey, operation.Key, operation.ItemKey, entity as FeedEntity);
                    postOperation.Entity.ETag       = preResult.ETag;
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Delete:
                case OperationType.DeleteIfExists:
                    FeedEntity deleteEntity = new FeedEntity();
                    deleteEntity.ETag = preResult.ETag;
                    return(Operation.Delete(table as FeedTable, operation.PartitionKey, operation.Key, operation.ItemKey, deleteEntity));
                }
            }
            else if (table is CountTable)
            {
                switch (operationType)
                {
                case OperationType.Insert:
                    postOperation = Operation.Insert(table as CountTable, operation.PartitionKey, operation.Key, (entity as CountEntity).Count);
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Replace:
                case OperationType.InsertOrReplace:
                    postOperation                   = Operation.Replace(table as CountTable, operation.PartitionKey, operation.Key, entity as CountEntity);
                    postOperation.Entity.ETag       = preResult.ETag;
                    postOperation.Entity.CustomETag = result.ETag;
                    return(postOperation);

                case OperationType.Increment:
                case OperationType.InsertOrIncrement:
                    postOperation = Operation.Replace(table as CountTable, operation.PartitionKey, operation.Key, new CountEntity()
                    {
                        Count = (double)result.Value
                    });
                    postOperation.Entity.ETag       = preResult.ETag;
                    postOperation.Entity.CustomETag = result.ETag;
                    break;

                case OperationType.Delete:
                case OperationType.DeleteIfExists:
                    CountEntity deleteEntity = new CountEntity();
                    deleteEntity.ETag = preResult.ETag;
                    return(Operation.Delete(table as CountTable, operation.PartitionKey, operation.Key, deleteEntity));
                }
            }

            return(postOperation);
        }
 /// <summary>
 /// Validate count table parameters and throw exceptions
 /// </summary>
 /// <param name="table">Count table</param>
 /// <param name="partitionKey">Partition key</param>
 /// <param name="countKey">Count key</param>
 /// <param name="entity">Count entity</param>
 private static void ValidateCountTableParameters(Table table, string partitionKey, string countKey, CountEntity entity)
 {
     ValidateCountTableParameters(table, partitionKey, countKey);
     if (entity == null)
     {
         throw new ArgumentNullException("Entity cannot be null");
     }
 }
 /// <summary>
 /// Replace operation on count table
 /// </summary>
 /// <param name="table">Count table</param>
 /// <param name="partitionKey">Partition key</param>
 /// <param name="countKey">Key for entity</param>
 /// <param name="entity">Count entity</param>
 /// <returns>Table operation</returns>
 public static Operation Replace(CountTable table, string partitionKey, string countKey, CountEntity entity)
 {
     ValidateCountTableParameters(table, partitionKey, countKey, entity);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.Replace,
         PartitionKey = partitionKey,
         Key = countKey,
         Entity = entity.Clone()
     });
 }
 /// <summary>
 /// Delete operation on count table
 /// </summary>
 /// <param name="table">Count table</param>
 /// <param name="partitionKey">Partition key for entity</param>
 /// <param name="countKey">Key for entity</param>
 /// <param name="entity">Count entity</param>
 /// <returns>Table operation</returns>
 public static Operation Delete(CountTable table, string partitionKey, string countKey, CountEntity entity = null)
 {
     ValidateCountTableParameters(table, partitionKey, countKey);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.Delete,
         PartitionKey = partitionKey,
         Key = countKey,
         Entity = entity != null?entity.Clone() : null
     });
 }