Skip to content

Commit

Permalink
SourceList Overloads for MergeChangeSets for Cache ChangeSets (#763)
Browse files Browse the repository at this point in the history
* Added some helper overloads for SourceList to Cache ChangeSet

* Fix unit tests

* Now with API Testing Goodness

* Support both List and List ChangeSet Merging
  • Loading branch information
dwcullop committed Nov 23, 2023
1 parent 0e1b8f8 commit f6eadfe
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 2 deletions.
6 changes: 4 additions & 2 deletions codecov.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
coverage:
codecov:
coverage:
status: #Code coverage status will be posted to pull requests based on targets defined below.
comments: off #Optional. Details are off by default.
comments: off #Optional. Details are off by default.
flags: []
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,18 @@ namespace DynamicData
where TGroupKey : notnull { }
public static System.IObservable<System.Collections.Generic.IEnumerable<T>> LimitSizeTo<T>(this DynamicData.ISourceList<T> source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<TDestination> MergeMany<T, TDestination>(this System.IObservable<DynamicData.IChangeSet<T>> source, System.Func<T, System.IObservable<TDestination>> observableSelector)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>> MergeManyChangeSets<TObject, TDestination, TDestinationKey>(this System.IObservable<DynamicData.IChangeSet<TObject>> source, System.Func<TObject, System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>>> observableSelector, System.Collections.Generic.IComparer<TDestination> comparer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,18 @@ namespace DynamicData
where TGroupKey : notnull { }
public static System.IObservable<System.Collections.Generic.IEnumerable<T>> LimitSizeTo<T>(this DynamicData.ISourceList<T> source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<TDestination> MergeMany<T, TDestination>(this System.IObservable<DynamicData.IChangeSet<T>> source, System.Func<T, System.IObservable<TDestination>> observableSelector)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>> MergeManyChangeSets<TObject, TDestination, TDestinationKey>(this System.IObservable<DynamicData.IChangeSet<TObject>> source, System.Func<TObject, System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>>> observableSelector, System.Collections.Generic.IComparer<TDestination> comparer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,18 @@ namespace DynamicData
where TGroupKey : notnull { }
public static System.IObservable<System.Collections.Generic.IEnumerable<T>> LimitSizeTo<T>(this DynamicData.ISourceList<T> source, int sizeLimit, System.Reactive.Concurrency.IScheduler? scheduler = null)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this DynamicData.IObservableList<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<System.IObservable<DynamicData.IChangeSet<TObject, TKey>>>> source, System.Collections.Generic.IEqualityComparer<TObject>? equalityComparer = null, System.Collections.Generic.IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull { }
public static System.IObservable<TDestination> MergeMany<T, TDestination>(this System.IObservable<DynamicData.IChangeSet<T>> source, System.Func<T, System.IObservable<TDestination>> observableSelector)
where T : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>> MergeManyChangeSets<TObject, TDestination, TDestinationKey>(this System.IObservable<DynamicData.IChangeSet<TObject>> source, System.Func<TObject, System.IObservable<DynamicData.IChangeSet<TDestination, TDestinationKey>>> observableSelector, System.Collections.Generic.IComparer<TDestination> comparer)
Expand Down
28 changes: 28 additions & 0 deletions src/DynamicData.Tests/List/MergeManyCacheChangeSetsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public void NullChecks()
var nullChangeSetObs = (IObservable<IChangeSet<int>>)null!;
var emptySelector = new Func<int, IObservable<IChangeSet<string, string>>>(i => Observable.Empty<IChangeSet<string, string>>());
var nullSelector = (Func<int, IObservable<IChangeSet<string, string>>>)null!;
var nullListOfCacheChangeSets = (ISourceList<IObservable<IChangeSet<string, string>>>)null!;
var emptyListOfCacheChangeSets = new SourceList<IObservable<IChangeSet<string, string>>>();
var nullChildComparer = (IComparer<string>)null!;
var emptyChildComparer = new NoOpComparer<string>() as IComparer<string>;
var emptyEqualityComparer = new NoOpEqualityComparer<string>() as IEqualityComparer<string>;
Expand All @@ -59,15 +61,18 @@ public void NullChecks()
var actionComparer0 = () => nullChangeSetObs.MergeManyChangeSets(emptySelector, comparer: emptyChildComparer);
var actionComparer1 = () => emptyChangeSetObs.MergeManyChangeSets(nullSelector, comparer: emptyChildComparer);
var actionComparer2 = () => emptyChangeSetObs.MergeManyChangeSets(emptySelector, comparer: nullChildComparer);
var actionMergeChangeSets0 = () => nullListOfCacheChangeSets.MergeChangeSets(comparer: emptyChildComparer);

// then
emptyChangeSetObs.Should().NotBeNull();
emptyChildComparer.Should().NotBeNull();
emptyEqualityComparer.Should().NotBeNull();
emptySelector.Should().NotBeNull();
emptyListOfCacheChangeSets.Should().NotBeNull();
nullChangeSetObs.Should().BeNull();
nullChildComparer.Should().BeNull();
nullSelector.Should().BeNull();
nullListOfCacheChangeSets.Should().BeNull();

actionDefault0.Should().Throw<ArgumentNullException>();
actionDefault1.Should().Throw<ArgumentNullException>();
Expand Down Expand Up @@ -708,6 +713,29 @@ public void MergedObservableWillFailIfSourceFails()
receivedError.Should().Be(expectedError);
}

[Fact]
public void SourceListMergeCacheChangeSets()
{
// having
var markets = Enumerable.Range(0, MarketCount).Select(n => new Market(n)).ToArray();
using var changeSetList = _marketList.Connect().Transform(m => m.LatestPrices).AsObservableList();
using var results = changeSetList.MergeChangeSets(MarketPrice.EqualityComparer).AsAggregator();
_marketList.AddRange(markets);
markets.ForEach((m, index) => m.AddUniquePrices(index, PricesPerMarket, ItemIdStride, GetRandomPrice));

// when
markets.ForEach(m => _marketList.Remove(m));

// then
_marketListResults.Data.Count.Should().Be(0);
markets.Sum(m => m.PricesCache.Count).Should().Be(MarketCount * PricesPerMarket);
results.Data.Count.Should().Be(0);
results.Messages.Count.Should().Be(MarketCount * 2);
results.Summary.Overall.Adds.Should().Be(MarketCount * PricesPerMarket);
results.Summary.Overall.Removes.Should().Be(MarketCount * PricesPerMarket);
results.Summary.Overall.Updates.Should().Be(0);
}

public void Dispose()
{
_marketListResults.Dispose();
Expand Down
70 changes: 70 additions & 0 deletions src/DynamicData/List/ObservableListEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,76 @@ public static IObservable<TDestination> MergeMany<T, TDestination>(this IObserva
return new MergeMany<T, TDestination>(source, observableSelector).Run();
}

/// <summary>
/// Merges each Observable ChangeSet in the ObservableList into a single stream of ChangeSets that correctly handles multiple Keys and removal of the parent items.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the object key.</typeparam>
/// <param name="source">The SourceList of Observable Cache ChangeSets.</param>
/// <param name="comparer"><see cref="IComparer{T}"/> instance to determine which element to emit if the same key is emitted from multiple child changesets.</param>
/// <returns>The result from merging the child changesets together.</returns>
/// <exception cref="ArgumentNullException">Parameter was null.</exception>
public static IObservable<IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this IObservableList<IObservable<IChangeSet<TObject, TKey>>> source, IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull
{
return source.Connect().MergeChangeSets(comparer: comparer);
}

/// <summary>
/// Merges all of the Cache Observable ChangeSets into a single ChangeSets while correctly handling multiple Keys and removal of the parent items.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the object key.</typeparam>
/// <param name="source">The SourceList of Observable Cache ChangeSets.</param>
/// <param name="equalityComparer">Optional <see cref="IEqualityComparer{T}"/> instance to determine if two elements are the same.</param>
/// <param name="comparer">Optional <see cref="IComparer{T}"/> instance to determine which element to emit if the same key is emitted from multiple child changesets.</param>
/// <returns>The result from merging the child changesets together.</returns>
/// <exception cref="ArgumentNullException">Parameter was null.</exception>
public static IObservable<IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this IObservableList<IObservable<IChangeSet<TObject, TKey>>> source, IEqualityComparer<TObject>? equalityComparer = null, IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull
{
return source.Connect().MergeChangeSets(equalityComparer, comparer);
}

/// <summary>
/// Merges all of the Cache Observable ChangeSets into a single ChangeSets while correctly handling multiple Keys and removal of the parent items.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the object key.</typeparam>
/// <param name="source">The List Observable ChangeSet of Cache Observable ChangeSets.</param>
/// <param name="comparer"><see cref="IComparer{T}"/> instance to determine which element to emit if the same key is emitted from multiple child changesets.</param>
/// <returns>The result from merging the child changesets together.</returns>
/// <exception cref="ArgumentNullException">Parameter was null.</exception>
public static IObservable<IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this IObservable<IChangeSet<IObservable<IChangeSet<TObject, TKey>>>> source, IComparer<TObject> comparer)
where TObject : notnull
where TKey : notnull
{
if (comparer == null) throw new ArgumentNullException(nameof(comparer));

return source.MergeChangeSets(comparer: comparer);
}

/// <summary>
/// Merges each Observable ChangeSet in the ObservableList into a single stream of ChangeSets that correctly handles multiple Keys and removal of the parent items.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <typeparam name="TKey">The type of the object key.</typeparam>
/// <param name="source">The List Observable ChangeSet of Cache Observable ChangeSets.</param>
/// <param name="equalityComparer">Optional <see cref="IEqualityComparer{T}"/> instance to determine if two elements are the same.</param>
/// <param name="comparer">Optional <see cref="IComparer{T}"/> instance to determine which element to emit if the same key is emitted from multiple child changesets.</param>
/// <returns>The result from merging the child changesets together.</returns>
/// <exception cref="ArgumentNullException">Parameter was null.</exception>
public static IObservable<IChangeSet<TObject, TKey>> MergeChangeSets<TObject, TKey>(this IObservable<IChangeSet<IObservable<IChangeSet<TObject, TKey>>>> source, IEqualityComparer<TObject>? equalityComparer = null, IComparer<TObject>? comparer = null)
where TObject : notnull
where TKey : notnull
{
if (source == null) throw new ArgumentNullException(nameof(source));

return source.MergeManyChangeSets(static src => src, equalityComparer, comparer);
}

/// <summary>
/// Operator similiar to MergeMany except it is ChangeSet aware. It uses <paramref name="observableSelector"/> to transform each item in the source into a child <see cref="IChangeSet{TDestination, TDestinationKey}"/> and merges the result children together into a single stream of ChangeSets that correctly handles multiple Keys and removal of the parent items.
/// </summary>
Expand Down

0 comments on commit f6eadfe

Please sign in to comment.