diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt index 5aa67a234..54782cbd8 100644 --- a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt @@ -2039,6 +2039,14 @@ namespace DynamicData where TDestination : class where TSource : notnull where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } + public static System.IObservable> TransformWithInlineUpdate(this System.IObservable> source, System.Func transformFactory, System.Action updateAction, System.Action> errorHandler, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull { } public static System.IObservable> TreatMovesAsRemoveAdd(this System.IObservable> source) where TObject : notnull where TKey : notnull { } diff --git a/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs b/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs index 170ffadf2..88249e34a 100644 --- a/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs +++ b/src/DynamicData.Tests/Cache/TransformWithInlineUpdateFixture.cs @@ -76,9 +76,34 @@ public void Remove() stub.Results.Data.Count.Should().Be(0, "Should be nothing cached"); } + + [Fact] + public void TransformOnRefresh() + { + using var stub = new TransformWithInlineUpdateFixtureStub(true); + var person = new Person("Adult1", 50); + stub.Source.AddOrUpdate(person); + + var transformedPerson = stub.Results.Data.Items.First(); + + person.Age = 51; + stub.Source.Refresh(person); + + var updatedTransform = stub.Results.Data.Items.First(); + + updatedTransform.Age.Should().Be(51, "Age should be updated from 50 to 51."); + stub.Results.Messages.Count.Should().Be(2, "Should be 2 updates"); + stub.Results.Data.Count.Should().Be(1, "Should be 1 item in the cache"); + transformedPerson.Should().Be(stub.Results.Data.Items.First(), "Should be same transformed person instance."); + } + private class TransformWithInlineUpdateFixtureStub : IDisposable { - public TransformWithInlineUpdateFixtureStub() => Results = new ChangeSetAggregator(Source.Connect().TransformWithInlineUpdate(TransformFactory, UpdateAction)); + public TransformWithInlineUpdateFixtureStub(bool transformOnRefresh = false) + { + Results = new ChangeSetAggregator(Source.Connect() + .TransformWithInlineUpdate(TransformFactory, UpdateAction, transformOnRefresh)); + } public ChangeSetAggregator Results { get; } diff --git a/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs b/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs index 0d747a43a..8efde6da3 100644 --- a/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs +++ b/src/DynamicData/Cache/Internal/TransformWithInlineUpdate.cs @@ -11,7 +11,8 @@ namespace DynamicData.Cache.Internal; internal sealed class TransformWithInlineUpdate(IObservable> source, Func transformFactory, Action updateAction, - Action>? exceptionCallback = null) + Action>? exceptionCallback = null, + bool transformOnRefresh = false) where TDestination : class where TSource : notnull where TKey : notnull @@ -41,7 +42,15 @@ private IObservable> RunImpl() => source.Scan( break; case ChangeReason.Refresh: - cache.Refresh(change.Key); + if (transformOnRefresh) + { + InlineUpdate(cache, change); + } + else + { + cache.Refresh(change.Key); + } + break; case ChangeReason.Moved: diff --git a/src/DynamicData/Cache/ObservableCacheEx.cs b/src/DynamicData/Cache/ObservableCacheEx.cs index d933c83b3..b33f1b845 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.cs @@ -5914,7 +5914,35 @@ public static IObservable> TransformWithInlineUpd transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); - return new TransformWithInlineUpdate(source, transformFactory, updateAction).Run(); + return source.TransformWithInlineUpdate(transformFactory, updateAction, false); + } + + /// + /// Projects each update item to a new form using the specified transform function and when an update is received, allows the preservation of the previous instance. + /// + /// The type of the destination. + /// The type of the source. + /// The type of the key. + /// The source. + /// The transform factory. + /// Apply changes to the original. Example (previousTransformedItem, newOriginalItem) => previousTransformedItem.Value = newOriginalItem. + /// Should a new transform be applied when a refresh event is received. + /// + /// A transformed update collection. + /// + /// source + /// or + /// transformFactory. + public static IObservable> TransformWithInlineUpdate(this IObservable> source, Func transformFactory, Action updateAction, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull + { + source.ThrowArgumentNullExceptionIfNull(nameof(source)); + transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + + return new TransformWithInlineUpdate(source, transformFactory, updateAction, transformOnRefresh: transformOnRefresh).Run(); } /// @@ -5943,7 +5971,37 @@ public static IObservable> TransformWithInlineUpd updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); - return new TransformWithInlineUpdate(source, transformFactory, updateAction, errorHandler).Run(); + return source.TransformWithInlineUpdate(transformFactory, updateAction, errorHandler, false); + } + + /// + /// Projects each update item to a new form using the specified transform function and when an update is received, allows the preservation of the previous instance. + /// + /// The type of the destination. + /// The type of the source. + /// The type of the key. + /// The source. + /// The transform factory. + /// Apply changes to the original. Example (previousTransformedItem, newOriginalItem) => previousTransformedItem.Value = newOriginalItem. + /// The error handler. + /// Should a new transform be applied when a refresh event is received. + /// + /// A transformed update collection. + /// + /// source + /// or + /// transformFactory. + public static IObservable> TransformWithInlineUpdate(this IObservable> source, Func transformFactory, Action updateAction, Action> errorHandler, bool transformOnRefresh) + where TDestination : class + where TSource : notnull + where TKey : notnull + { + source.ThrowArgumentNullExceptionIfNull(nameof(source)); + transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory)); + updateAction.ThrowArgumentNullExceptionIfNull(nameof(updateAction)); + errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler)); + + return new TransformWithInlineUpdate(source, transformFactory, updateAction, errorHandler, transformOnRefresh).Run(); } ///