/// <summary> /// This method attempts to returned a cached value, while /// simultaneously calling a Func to return the latest value. When the /// latest data comes back, it replaces what was previously in the /// cache. /// /// This method is best suited for loading dynamic data from the /// Internet, while still showing the user earlier data. /// /// This method returns an IObservable that may return *two* results /// (first the cached data, then the latest data). Therefore, it's /// important for UI applications that in your Subscribe method, you /// write the code to merge the second result when it comes in. /// </summary> /// <param name="key">The key to store the returned result under.</param> /// <param name="fetchFunc"></param> /// <param name="fetchPredicate">An optional Func to determine whether /// the updated item should be fetched. If the cached version isn't found, /// this parameter is ignored and the item is always fetched.</param> /// <param name="absoluteExpiration">An optional expiration date.</param> /// <returns>An Observable stream containing either one or two /// results (possibly a cached version, then the latest version)</returns> public static IObservable <T> GetAndFetchLatest <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, Func <DateTimeOffset, bool> fetchPredicate = null, DateTimeOffset?absoluteExpiration = null) { bool foundItemInCache; var fail = Observable.Defer(() => This.GetCreatedAt(key)) .Select(x => fetchPredicate != null && x != null ? fetchPredicate(x.Value) : true) .Where(x => x != false) .SelectMany(_ => fetchFunc()) .Finally(() => This.Invalidate(key)) .Do(x => This.InsertObject(key, x, absoluteExpiration)); var result = This.GetObjectAsync <T>(key).Select(x => new Tuple <T, bool>(x, true)) .Catch(Observable.Return(new Tuple <T, bool>(default(T), false))); return(result.SelectMany(x => { foundItemInCache = x.Item2; return x.Item2 ? Observable.Return(x.Item1) : Observable.Empty <T>(); }).Concat(fail).Multicast(new ReplaySubject <T>()).RefCount()); }
/// <summary> /// Attempt to return an object from the cache. If the item doesn't /// exist or returns an error, call a Func to return the latest /// version of an object and insert the result in the cache. /// /// For most Internet applications, this method is the best method to /// call to fetch static data (i.e. images) from the network. /// </summary> /// <param name="key">The key to associate with the object.</param> /// <param name="fetchFunc">A Func which will asynchronously return /// the latest value for the object should the cache not contain the /// key. /// /// Observable.Start is the most straightforward way (though not the /// most efficient!) to implement this Func.</param> /// <param name="absoluteExpiration">An optional expiration date.</param> /// <returns>A Future result representing the deserialized object from /// the cache.</returns> public static IObservable <T> GetOrFetchObject <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, DateTimeOffset?absoluteExpiration = null) { return(This.GetObjectAsync <T>(key).Catch <T, Exception>(_ => { object dontcare; return ((IObservable <T>)inflightFetchRequests.GetOrAdd(key, __ => (object)fetchFunc())) .Do(x => This.InsertObject(key, x, absoluteExpiration)) .Finally(() => inflightFetchRequests.TryRemove(key, out dontcare)) .Multicast(new AsyncSubject <T>()).RefCount(); })); }
/// <summary> /// Return all objects of a specific Type in the cache. /// </summary> /// <returns>A Future result representing all objects in the cache /// with the specified Type.</returns> public static IObservable <IEnumerable <T> > GetAllObjects <T>(this IBlobCache This) { // NB: This isn't exactly thread-safe, but it's Close Enough(tm) // We make up for the fact that the keys could get kicked out // from under us via the Catch below var matchingKeys = This.GetAllKeys() .Where(x => x.StartsWith(GetTypePrefixedKey("", typeof(T)))) .ToArray(); return(matchingKeys.ToObservable() .SelectMany(x => This.GetObjectAsync <T>(x, true).Catch(Observable.Empty <T>())) .ToList() .Select(x => (IEnumerable <T>)x)); }
/// <summary> /// Attempt to return an object from the cache. If the item doesn't /// exist or returns an error, call a Func to return the latest /// version of an object and insert the result in the cache. /// /// For most Internet applications, this method is the best method to /// call to fetch static data (i.e. images) from the network. /// </summary> /// <param name="key">The key to associate with the object.</param> /// <param name="fetchFunc">A Func which will asynchronously return /// the latest value for the object should the cache not contain the /// key. /// /// Observable.Start is the most straightforward way (though not the /// most efficient!) to implement this Func.</param> /// <param name="absoluteExpiration">An optional expiration date.</param> /// <returns>A Future result representing the deserialized object from /// the cache.</returns> public static IObservable <T> GetOrFetchObject <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, DateTimeOffset?absoluteExpiration = null) { return(This.GetObjectAsync <T>(key).Catch <T, Exception>(_ => { object dontcare; var prefixedKey = This.GetHashCode().ToString() + key; var result = Observable.Defer(() => fetchFunc()) .Do(x => This.InsertObject(key, x, absoluteExpiration)) .Finally(() => inflightFetchRequests.TryRemove(prefixedKey, out dontcare)) .Multicast(new AsyncSubject <T>()).RefCount(); return (IObservable <T>)inflightFetchRequests.GetOrAdd(prefixedKey, result); })); }
public static IObservable <IDictionary <string, T> > GetObjectsAsync <T>(this IBlobCache This, IEnumerable <string> keys, bool noTypePrefix = false) { var bulkCache = This as IObjectBulkBlobCache; if (bulkCache != null) { return(bulkCache.GetObjectsAsync <T>(keys)); } return(keys.ToObservable() .SelectMany(x => { return This.GetObjectAsync <T>(x) .Select(y => new KeyValuePair <string, T>(x, y)) .Catch <KeyValuePair <string, T>, KeyNotFoundException>(_ => Observable.Empty <KeyValuePair <string, T> >()); }) .ToDictionary(k => k.Key, v => v.Value)); }
/// <summary> /// Return all objects of a specific Type in the cache. /// </summary> /// <returns>A Future result representing all objects in the cache /// with the specified Type.</returns> public static IObservable <IEnumerable <T> > GetAllObjects <T>(this IBlobCache This) { var objCache = This as IObjectBlobCache; if (objCache != null) { return(objCache.GetAllObjects <T>()); } // NB: This isn't exactly thread-safe, but it's Close Enough(tm) // We make up for the fact that the keys could get kicked out // from under us via the Catch below var matchingKeys = This.GetAllKeys() .ToArray(); return(matchingKeys.ToObservable() .SelectMany(x => This.GetObjectAsync <T>(x, true).Catch(Observable.Empty <T>())) .ToList() .Where(x => x is T) .Select(x => (IEnumerable <T>)x)); }
/// <summary> /// This method attempts to returned a cached value, while /// simultaneously calling a Func to return the latest value. When the /// latest data comes back, it replaces what was previously in the /// cache. /// /// This method is best suited for loading dynamic data from the /// Internet, while still showing the user earlier data. /// /// This method returns an IObservable that may return *two* results /// (first the cached data, then the latest data). Therefore, it's /// important for UI applications that in your Subscribe method, you /// write the code to merge the second result when it comes in. /// /// This also means that await'ing this method is a Bad Idea(tm), always /// use Subscribe. /// </summary> /// <param name="key">The key to store the returned result under.</param> /// <param name="fetchFunc"></param> /// <param name="fetchPredicate">An optional Func to determine whether /// the updated item should be fetched. If the cached version isn't found, /// this parameter is ignored and the item is always fetched.</param> /// <param name="absoluteExpiration">An optional expiration date.</param> /// <param name="shouldInvalidateOnError">If this is true, the cache will /// be cleared when an exception occurs in fetchFunc</param> /// <returns>An Observable stream containing either one or two /// results (possibly a cached version, then the latest version)</returns> public static IObservable <T> GetAndFetchLatest <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, Func <DateTimeOffset, bool> fetchPredicate = null, DateTimeOffset?absoluteExpiration = null, bool shouldInvalidateOnError = false) { var fetch = Observable.Defer(() => This.GetObjectCreatedAt <T>(key)) .Select(x => fetchPredicate == null || x == null || fetchPredicate(x.Value)) .Where(x => x != false) .SelectMany(async _ => { var ret = default(T); try { ret = await fetchFunc(); } catch (Exception) { if (shouldInvalidateOnError) { This.InvalidateObject <T>(key); } throw; } await This.InvalidateObject <T>(key); await This.InsertObject(key, ret, absoluteExpiration); return(ret); }); var result = This.GetObjectAsync <T>(key).Select(x => new Tuple <T, bool>(x, true)) .Catch(Observable.Return(new Tuple <T, bool>(default(T), false))); return(result.SelectMany(x => { return x.Item2 ? Observable.Return(x.Item1) : Observable.Empty <T>(); }).Concat(fetch).Multicast(new ReplaySubject <T>()).RefCount()); }