You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Feature]: IObservableCache.TransformMany with force transform param聽#899
Currently only Transform has an overload which supports refreshing the derived cache with another observable. This is very useful when doing a transform which depends on external to the cache data which can be changed, thus invalidating the derived cache data, but TransformMany doesn't have a similar override.
Considerations
Refreshing the original source on external data changes may be not desirable due to other effects and also isn't obvious
It's probably missing from TransformMany because of performance. Having to re-transform every single value is expensive and it would be worse if each value transforms into a collection of values.
My suggestion would be to check out one of the new operators such as TransformManyAsync or MergeManyChangeSets. TransformManyAsync allows you to transform each value into an ObservableCollection or a SourceCache of values which all all merged together.
MergeManyChangeSets allows you to transform each value into a ChangeSet, which is all merged together.
When your external data is updated, it can update the changeset, and then results will automatically be propagated.
This will be a lot more performant that just transforming every single value over and over again.
In my case I don't really care about performance this much since I am evaluating this once when the source values are edited from UI, and I had to introduce another observable stream to update because conditions which impact calculation of TransformMany have changed.
What I did in the end was to snatch the refresh logic and introduce it as a separate .RefreshOn(IObservable<> refresher) operator which I can use wherever I need. Works flawlessly and I think could be a good addition to the library.
publicstaticIObservable<IChangeSet<TSource,TKey>>RefreshOn<TSource,TKey>(thisIObservable<IChangeSet<TSource,TKey>>source,IObservable<Func<TSource,TKey,bool>>refresher)whereTSource:notnullwhereTKey:notnull=>Observable.Create<IChangeSet<TSource,TKey>>(observer =>{varlocker=newobject();varshared=source.Synchronize(locker).Publish();// capture all items so we can apply a forced transformvarcache=newDictionary<TKey,TSource>();varcacheLoader=shared.Subscribe(changes =>{foreach(varitemin(changesasChangeSet<TSource,TKey>)!){switch(item.Reason){caseChangeReason.Update:caseChangeReason.Add:cache[item.Key]=item.Current;break;caseChangeReason.Remove:cache.Remove(item.Key);break;}}});// create change set of items where force refresh is appliedvarrefreshes=refresher.Synchronize(locker).Select(selector =>cache.Where(kvp =>selector(kvp.Value,kvp.Key)).Select(kvp =>newChange<TSource,TKey>(ChangeReason.Refresh,kvp.Key,kvp.Value))).Select(changes =>newChangeSet<TSource,TKey>(changes)).NotEmpty();varsourceAndRefreshes=shared.Merge(refreshes);returnnewCompositeDisposable(cacheLoader,sourceAndRefreshes.SubscribeSafe(observer),shared.Connect());});publicstaticIObservable<IChangeSet<TSource,TKey>>RefreshOn<TSource,TKey>(thisIObservable<IChangeSet<TSource,TKey>>source,IObservable<Func<TSource,bool>>refresher)whereTSource:notnullwhereTKey:notnull=>source.RefreshOn(refresher.Select(selector =>newFunc<TSource,TKey,bool>((value,_)=>selector(value))));publicstaticIObservable<IChangeSet<TSource,TKey>>RefreshOn<TSource,TKey>(thisIObservable<IChangeSet<TSource,TKey>>source,IObservable<Unit>refresher)whereTSource:notnullwhereTKey:notnull=>source.RefreshOn(refresher.Select(_ =>newFunc<TSource,TKey,bool>((_,_)=>true)));
If that's all you needed, have you considered AutoRefreshOnObservable? It is very similar except structured a little differently. Having an IObservable<Func<T, TKey, bool>> seems a little unusual.
If you really think it should be part of the library, you should consider submitting a PR that wraps that logic in a class with a Run method (which is Roland's convention) and some appropriate unit tests.
But you should also worry about performance because it will need to work for every situation, not just yours.
Activity
dwcullop commentedon May 12, 2024
I'm assuming you're talking about the
IObservable<Unit> forceTransformparameter found in this overload.IObservable<IChangeSet<TDestination, TKey>> Transform<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, TKey, TDestination> transformFactory, IObservable<Unit> forceTransform)It's probably missing from
TransformManybecause of performance. Having to re-transform every single value is expensive and it would be worse if each value transforms into a collection of values.My suggestion would be to check out one of the new operators such as
TransformManyAsyncorMergeManyChangeSets.TransformManyAsyncallows you to transform each value into an ObservableCollection or a SourceCache of values which all all merged together.MergeManyChangeSetsallows you to transform each value into a ChangeSet, which is all merged together.When your external data is updated, it can update the changeset, and then results will automatically be propagated.
This will be a lot more performant that just transforming every single value over and over again.
Metadorius commentedon May 12, 2024
In my case I don't really care about performance this much since I am evaluating this once when the source values are edited from UI, and I had to introduce another observable stream to update because conditions which impact calculation of
TransformManyhave changed.What I did in the end was to snatch the refresh logic and introduce it as a separate
.RefreshOn(IObservable<> refresher)operator which I can use wherever I need. Works flawlessly and I think could be a good addition to the library.dwcullop commentedon Sep 17, 2024
If that's all you needed, have you considered
AutoRefreshOnObservable? It is very similar except structured a little differently. Having anIObservable<Func<T, TKey, bool>>seems a little unusual.If you really think it should be part of the library, you should consider submitting a PR that wraps that logic in a class with a
Runmethod (which is Roland's convention) and some appropriate unit tests.But you should also worry about performance because it will need to work for every situation, not just yours.