/// <summary>
        /// Query feed item async
        /// </summary>
        /// <typeparam name="T">Feed entity</typeparam>
        /// <param name="table">Feed table</param>
        /// <param name="partitionKey">Partition key for feed</param>
        /// <param name="feedKey">Key for feed</param>
        /// <param name="itemKey">Item key for feed item</param>
        /// <returns>Feed entity in feed</returns>
        public async Task <T> QueryFeedItemAsync <T>(FeedTable table, string partitionKey, string feedKey, string itemKey)
            where T : FeedEntity, new()
        {
            this.ValidateQueryFeedItemParameters(table, partitionKey, feedKey, itemKey);
            switch (table.StorageMode)
            {
            case StorageMode.PersistentOnly:
                return(await this.persistentStore.QueryFeedItemAsync <T>(table, partitionKey, feedKey, itemKey));

            case StorageMode.CacheOnly:
                return(await this.cache.QueryFeedItemAsync <T>(table, partitionKey, feedKey, itemKey));

            case StorageMode.Default:
                T cacheEntity = await this.cache.QueryFeedItemAsync <T>(table, partitionKey, feedKey, itemKey);

                if (this.IsCacheEntityNullOrInvalid(cacheEntity))
                {
                    T persistentEntity = await this.persistentStore.QueryFeedItemAsync <T>(table, partitionKey, feedKey, itemKey);

                    return(persistentEntity);
                }

                return(cacheEntity);

            default:
                throw new NotSupportedException();
            }
        }
        /// <summary>
        /// Query feed async
        /// </summary>
        /// <typeparam name="T">Feed entity</typeparam>
        /// <param name="table">Feed table</param>
        /// <param name="partitionKey">Partition key for feed</param>
        /// <param name="feedKey">Key for feed</param>
        /// <param name="cursor">Feed cursor</param>
        /// <param name="limit">Feed count limit</param>
        /// <returns>List of feed entities</returns>
        public async Task <IList <T> > QueryFeedAsync <T>(
            FeedTable table,
            string partitionKey,
            string feedKey,
            string cursor,
            int limit)
            where T : FeedEntity, new()
        {
            string startRowKey = this.GetStartRowKeyForFeed(table, feedKey, cursor);
            string endRowKey   = this.GetEndRowKeyForFeed(table, feedKey);

            string filterA   = TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey);
            string filterB   = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThan, startRowKey);
            string filterC   = TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, endRowKey);
            string filterAB  = TableQuery.CombineFilters(filterA, TableOperators.And, filterB);
            string filterABC = TableQuery.CombineFilters(filterAB, TableOperators.And, filterC);

            TableQuery <DynamicTableEntity> query = new TableQuery <DynamicTableEntity>().Where(filterABC);

            if (limit != int.MaxValue)
            {
                query = query.Take(limit);
            }

            CloudTable cloudTable = this.GetCloudTable(table);

            var feedEntities             = new List <T>();
            TableContinuationToken token = null;

            do
            {
                TableQuerySegment <DynamicTableEntity> segment = await cloudTable.ExecuteQuerySegmentedAsync <DynamicTableEntity>(query, token);

                token = segment.ContinuationToken;

                foreach (var item in segment)
                {
                    T entity = this.GetEntityFromDynamicTableEntity <T>(item);
                    entity.PartitionKey = partitionKey;
                    entity.FeedKey      = feedKey;
                    entity.ItemKey      = this.GetItemKey(table, feedKey, item.RowKey);
                    entity.Cursor       = entity.ItemKey;
                    feedEntities.Add(entity);
                }

                if (query.TakeCount != null)
                {
                    if (feedEntities.Count == query.TakeCount.Value)
                    {
                        break;
                    }
                }
            }while (token != null);

            return(feedEntities);
        }
 /// <summary>
 /// Delete if exists operation on feed table
 /// </summary>
 /// <param name="table">Feed table</param>
 /// <param name="partitionKey">Partition key for feed</param>
 /// <param name="feedKey">Key for feed</param>
 /// <param name="itemKey">Item key for feed entity</param>
 /// <returns>Table operation</returns>
 public static Operation DeleteIfExists(FeedTable table, string partitionKey, string feedKey, string itemKey)
 {
     ValidateFeedTableParameters(table, partitionKey, feedKey, itemKey);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.DeleteIfExists,
         PartitionKey = partitionKey,
         Key = feedKey,
         ItemKey = itemKey
     });
 }
 /// <summary>
 /// Insert if not empty operation on feed table
 /// </summary>
 /// <param name="table">Feed table</param>
 /// <param name="partitionKey">Partition key for feed</param>
 /// <param name="feedKey">Key for feed</param>
 /// <param name="itemKey">Item key for feed entity</param>
 /// <param name="entity">Feed entity</param>
 /// <returns>Table operation</returns>
 internal static Operation InsertIfNotEmpty(FeedTable table, string partitionKey, string feedKey, string itemKey, FeedEntity entity)
 {
     ValidateFeedTableParameters(table, partitionKey, feedKey, itemKey, entity);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.InsertIfNotEmpty,
         PartitionKey = partitionKey,
         Key = feedKey,
         ItemKey = itemKey,
         Entity = entity.Clone()
     });
 }
 /// <summary>
 /// Replace operation on feed table
 /// </summary>
 /// <param name="table">Feed table</param>
 /// <param name="partitionKey">Partition key for feed</param>
 /// <param name="feedKey">Key for feed</param>
 /// <param name="itemKey">Item key for feed entity</param>
 /// <param name="entity">Feed entity</param>
 /// <returns>Table operation</returns>
 public static Operation Replace(FeedTable table, string partitionKey, string feedKey, string itemKey, FeedEntity entity)
 {
     ValidateFeedTableParameters(table, partitionKey, feedKey, itemKey, entity);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.Replace,
         PartitionKey = partitionKey,
         Key = feedKey,
         ItemKey = itemKey,
         Entity = entity.Clone()
     });
 }
 /// <summary>
 /// Delete operation on feed table
 /// </summary>
 /// <param name="table">Feed table</param>
 /// <param name="partitionKey">Partition key for feed</param>
 /// <param name="feedKey">Key for feed</param>
 /// <param name="itemKey">Item key for feed entity</param>
 /// <param name="entity">Feed entity</param>
 /// <returns>Table operation</returns>
 public static Operation Delete(FeedTable table, string partitionKey, string feedKey, string itemKey, FeedEntity entity = null)
 {
     ValidateFeedTableParameters(table, partitionKey, feedKey, itemKey);
     return(new Operation()
     {
         Table = table,
         OperationType = OperationType.Delete,
         PartitionKey = partitionKey,
         Key = feedKey,
         ItemKey = itemKey,
         Entity = entity != null?entity.Clone() : null
     });
 }
        /// <summary>
        /// Query feed item async
        /// </summary>
        /// <typeparam name="T">Feed entity</typeparam>
        /// <param name="table">Feed table</param>
        /// <param name="partitionKey">Partition key for feed</param>
        /// <param name="feedKey">Key for feed</param>
        /// <param name="itemKey">Item key for feed entity</param>
        /// <returns>Feed entity result. Returns null if entity does not exist</returns>
        public async Task <T> QueryFeedItemAsync <T>(FeedTable table, string partitionKey, string feedKey, string itemKey)
            where T : FeedEntity, new()
        {
            string rowKey = this.GetRowKey(table, feedKey, itemKey);
            T      entity = await this.QueryTableEntity <T>(table, partitionKey, rowKey);

            if (entity == null)
            {
                return(null);
            }

            entity.FeedKey = feedKey;
            entity.ItemKey = itemKey;
            entity.Cursor  = entity.ItemKey;
            return(entity);
        }
        /// <summary>
        /// Query feed async
        /// </summary>
        /// <typeparam name="T">Feed entity</typeparam>
        /// <param name="table">Feed table</param>
        /// <param name="partitionKey">Partition for entity</param>
        /// <param name="feedKey">Key for feed</param>
        /// <param name="cursor">Feed cursor</param>
        /// <param name="limit">Feed count limit</param>
        /// <returns>List of feed entities</returns>
        public async Task <IList <T> > QueryFeedAsync <T>(
            FeedTable table,
            string partitionKey,
            string feedKey,
            string cursor,
            int limit)
            where T : FeedEntity, new()
        {
            this.ValidateQueryFeedParameters(table, partitionKey, feedKey, limit);
            switch (table.StorageMode)
            {
            case StorageMode.PersistentOnly:
                return(await this.persistentStore.QueryFeedAsync <T>(table, partitionKey, feedKey, cursor, limit));

            case StorageMode.CacheOnly:
                return(await this.cache.QueryFeedAsync <T>(table, partitionKey, feedKey, cursor, limit));

            case StorageMode.Default:
                IList <T> cacheEntities = await this.cache.QueryFeedAsync <T>(table, partitionKey, feedKey, cursor, limit);

                IList <T> invalidCacheEntities = cacheEntities.Where(e => this.IsCacheEntityNullOrInvalid(e)).ToList();
                IEnumerable <Task <T> > persistentInvalidEntitiesTasks = null;
                if (invalidCacheEntities.Count > 0)
                {
                    persistentInvalidEntitiesTasks = from entity in invalidCacheEntities select this.persistentStore.QueryFeedItemAsync <T>(table, partitionKey, feedKey, entity.ItemKey);
                }

                IList <T> persistentEntities = new List <T>();
                if (cacheEntities.Count < limit)
                {
                    string continuationCursor = cacheEntities.Count == 0 ? cursor : cacheEntities.Last().ItemKey;

                    // We add count of invalid cache entities to be conservative in case all the invalid cache entities are null
                    // We add one more to the limit so there is at least item in the cache when the client queries with last item key as cursor
                    // If there are no items in the cache for a query, the entities in persistent store is not cached since we don't know the
                    // the range of the items and whether it continues from items in the cache.
                    int continuationLimit = limit - cacheEntities.Count;
                    if (int.MaxValue - continuationLimit < invalidCacheEntities.Count + 1)
                    {
                        continuationLimit = int.MaxValue;
                    }
                    else
                    {
                        continuationLimit += invalidCacheEntities.Count + 1;
                    }

                    persistentEntities = await this.persistentStore.QueryFeedAsync <T>(table, partitionKey, feedKey, continuationCursor, continuationLimit);
                }

                List <T>         entities          = new List <T>();
                List <Operation> cachingOperations = new List <Operation>();
                T[] persistentInvalidEntities      = null;
                if (persistentInvalidEntitiesTasks != null)
                {
                    persistentInvalidEntities = await Task.WhenAll(persistentInvalidEntitiesTasks.ToArray());
                }

                int persistentInvalidEntityIndex = 0;
                foreach (T cacheEntity in cacheEntities)
                {
                    if (this.IsCacheEntityNullOrInvalid(cacheEntity))
                    {
                        T invalidPersistentEntity = persistentInvalidEntities[persistentInvalidEntityIndex++];
                        if (invalidPersistentEntity != null)
                        {
                            entities.Add(invalidPersistentEntity);
                        }

                        Operation cachingOperation = this.GetFeedCachingOperation(table, partitionKey, feedKey, cacheEntity.ItemKey, cacheEntity, invalidPersistentEntity, false);
                        if (cachingOperation != null)
                        {
                            cachingOperations.Add(cachingOperation);
                        }
                    }
                    else
                    {
                        entities.Add(cacheEntity);
                    }
                }

                foreach (T persistentEntity in persistentEntities)
                {
                    entities.Add(persistentEntity);

                    // Add caching operations only if the persistent feed items are continuation
                    // of items retrieved from cache or if cursor is null (starting of the feed)
                    if (cacheEntities.Count > 0 || cursor == null)
                    {
                        // The cache could evict the entire feed before we run the caching operations.
                        // If the cache already has feed items, the eviction combined with the caching operations
                        // can violate the feed cache invariant.
                        // Hence, we need to make sure the the feed is not empty
                        // when newer items are added to the cache.
                        // We don't need to make this check if the cursor is null and there are
                        // no items in the cache to start with.
                        bool      checkIfNotEmpty  = cacheEntities.Count != 0;
                        Operation cachingOperation = this.GetFeedCachingOperation(table, partitionKey, feedKey, persistentEntity.ItemKey, null, persistentEntity, checkIfNotEmpty);
                        cachingOperations.Add(cachingOperation);
                    }
                }

                await this.ExecuteCachingOperationsAsync(cachingOperations);

                return(entities.Take(limit).ToList());

            default:
                throw new NotSupportedException();
            }
        }