Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove from transform many with projection #265

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions src/DynamicData.Tests/List/TransformManyProjectionFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using DynamicData.Aggregation;
using DynamicData.Binding;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Xunit;

namespace DynamicData.Tests.List
{
public class TransformManyProjectionFixture : IDisposable
{
private readonly ISourceList<ClassWithNestedObservableCollection> _source;
private readonly IObservableList<ProjectedNestedChild> _results;

public TransformManyProjectionFixture()
{
_source = new SourceList<ClassWithNestedObservableCollection>();

_results = _source.Connect()
.AutoRefreshOnObservable(self => self.Children.ToObservableChangeSet())
.TransformMany(parent => parent.Children.Select(c => new ProjectedNestedChild(parent, c)), new ProjectNestedChildEqualityComparer())
.AsObservableList();
}

public void Dispose()
{
_source.Dispose();
}

[Fact]
public void AddRange()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

_results.Count.Should().Be(5);
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Take(5).Select(c => new ProjectedNestedChild(p, c))));
}

[Fact]
public void RemoveParent()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

//remove a parent and check children have moved
_source.Remove(parents[0]);
_results.Count.Should().Be(3);
_results.Items.ShouldBeEquivalentTo(parents.Skip(1).SelectMany(p => p.Children.Select(c => new ProjectedNestedChild(p, c))));
}

[Fact]
public void RemoveChild()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

//remove a child
parents[1].Children.Remove(children[3]);
_results.Count.Should().Be(4);
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Where(child => child.Name != "D").Select(c => new ProjectedNestedChild(p, c))));
}

private class ProjectedNestedChild
{
public ClassWithNestedObservableCollection Parent { get; }

public NestedChild Child { get; }

public ProjectedNestedChild(ClassWithNestedObservableCollection parent, NestedChild child)
{
Parent = parent;
Child = child;
}
}

private class ProjectNestedChildEqualityComparer : IEqualityComparer<ProjectedNestedChild>
{
public bool Equals(ProjectedNestedChild x, ProjectedNestedChild y)
{
if (x == null || y == null)
return false;

return x.Child.Name == y.Child.Name;
}

public int GetHashCode(ProjectedNestedChild obj)
{
return obj.Child.Name.GetHashCode();
}
}

private class NestedChild
{
public string Name { get; }
public string Value { get; }

public NestedChild(string name, string value)
{
Name = name;
Value = value;
}
}

private class ClassWithNestedObservableCollection
{
public int Id { get; }
public ObservableCollection<NestedChild> Children { get; }

public ClassWithNestedObservableCollection(int id, IEnumerable<NestedChild> animals)
{
Id = id;
Children = new ObservableCollection<NestedChild>(animals);
}
}
}
}
19 changes: 17 additions & 2 deletions src/DynamicData/List/Internal/TransformMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,23 @@ public IObservable<IChangeSet<TDestination>> Run()
return CreateWithChangeset();
}

return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
.Select(changes => new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer))).NotEmpty();
return Observable.Create<IChangeSet<TDestination>>(observer =>
{
//NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators
var result = new ChangeAwareList<TDestination>();

return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
.Select(changes =>
{
var destinationChanges = new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer));
result.Clone(destinationChanges, _equalityComparer);
return result.CaptureChanges();
})

.NotEmpty()
.SubscribeSafe(observer);
}
);
}

private IObservable<IChangeSet<TDestination>> CreateWithChangeset()
Expand Down
36 changes: 32 additions & 4 deletions src/DynamicData/List/ListEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ internal static bool MovedWithinRange<T>(this Change<T> source, int startIndex,
/// changes
/// </exception>
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)
{
Clone(source, changes, null);
}

/// <summary>
/// Clones the list from the specified change set
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="changes">The changes.</param>
/// <param name="equalityComparer">An equality comparer to match items in the changes.</param>
/// <exception cref="System.ArgumentNullException">
/// source
/// or
/// changes
/// </exception>
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes, IEqualityComparer<T> equalityComparer)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added an overload instead of an optional parameter because of https://github.com/dchaib/DynamicData/blob/e4e0327fd38c909f293e1bd9731049fdd78a73e0/src/DynamicData/List/ObservableListEx.cs#L1246
Let me know if I should fix this differently!

{
if (source == null)
{
Expand All @@ -58,11 +75,11 @@ public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)

foreach (var item in changes)
{
Clone(source, item);
Clone(source, item, equalityComparer ?? EqualityComparer<T>.Default);
}
}

private static void Clone<T>(this IList<T> source, Change<T> item)
private static void Clone<T>(this IList<T> source, Change<T> item, IEqualityComparer<T> equalityComparer)
{
var changeAware = source as ChangeAwareList<T>;

Expand Down Expand Up @@ -154,8 +171,19 @@ private static void Clone<T>(this IList<T> source, Change<T> item)
source.RemoveAt(change.CurrentIndex);
}
else
{
source.Remove(change.Current);
{
if (equalityComparer != null)
{
int index = source.IndexOf(change.Current, equalityComparer);
if (index > -1)
{
source.RemoveAt(index);
}
}
else
{
source.Remove(change.Current);
}
}

break;
Expand Down