DynamicData icon indicating copy to clipboard operation
DynamicData copied to clipboard

[possible Bug]: ChangeAwareCache not capturing initialized data

Open hutterm opened this issue 3 years ago • 2 comments

Describe the bug 🐞

looking through the code because of something unrelated the following took me by surprise:

https://github.com/reactivemarbles/DynamicData/blob/main/src/DynamicData/Cache/ChangeAwareCache.cs#L54

initializing the ChangeAwareCache with a Dictionary and subsequently calling CaptureChanges() did not produce an initial ChangeSet!

I don't know if that might have an effect on the whole library as for example the ChangeAwareList does have code for the initial ChangeSet

Step to reproduce

just code review

Reproduction repository

https://github.com/reactivemarbles/DynamicData

Expected behavior

to send an initial ChangeSet

Screenshots 🖼️

No response

IDE

No response

Operating system

No response

Version

No response

Device

No response

DynamicData Version

No response

Additional information ℹ️

No response

hutterm avatar Nov 30 '22 17:11 hutterm

That overload is intended as it is used by the source cache to ensure empty changeset when no subscribers are registered. That said it may be worth having an overload to explicitly suppress the notification when using this constructor

RolandPheasant avatar Nov 30 '22 21:11 RolandPheasant

Maybe I shall explain how I got to this: EFCore uses the ObservableHashSet and I wanted to use this with DynamicData. My first try was to use the ObservableCollectionEx.ToObservableChangeSet<TCollection,T>(...) overload, which wasn't very successful as that HashSet's change notifications don't really work for a list with order. So I basically rewrote it to match a Set, maybe this is of use for the library, feel free to add it in:

    public static IObservable<IChangeSet<TObject,TKey>> ToObservableChangeSet<TCollection,TObject,TKey>(this TCollection source, Func<TObject,TKey> key)
        where TCollection : INotifyCollectionChanged,ISet<TObject>
    {
        if (source is null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        return Observable.Create<IChangeSet<TObject,TKey>>(
            observer =>
            {
                var data = new ChangeAwareCache<TObject, TKey>();

                foreach (var item in source) data.Add(item, key(item)); // <----- this right here is necessary, because the ChangeAwareCache ctor doesn't produce an initial ChangeSet

                if (data.Count > 0)
                {
                    observer.OnNext(data.CaptureChanges());
                }

                return source.ObserveCollectionChanges().Scan(
                    data,
                    (cache, args) =>
                    {
                        var changes = args.EventArgs;
                        if (changes.NewItems is not null)
                        {
                            foreach (var item in changes.NewItems.Cast<TObject>())
                            {
                                cache.Add(item, key(item));
                            }
                        }

                        if (changes.OldItems is not null)
                        {
                            cache.Remove(changes.OldItems.Cast<TObject>().Select(key));
                        }
                        return cache;
                    }).Select(cache => cache.CaptureChanges()).SubscribeSafe(observer);
            });
    }

hutterm avatar Nov 30 '22 23:11 hutterm