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