/// <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 }); }