Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,14 @@ namespace DynamicData
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this System.IObservable<DynamicData.IChangeSet<TSource, TKey>> source, System.Func<TSource, DynamicData.Kernel.Optional<TSource>, TKey, System.Threading.CancellationToken, System.Threading.Tasks.Task<TDestination>> transformFactory, DynamicData.TransformAsyncOptions options)
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this System.IObservable<DynamicData.IChangeSet<TSource, TKey>> source, System.Func<TSource, DynamicData.Kernel.Optional<TSource>, TKey, System.Threading.CancellationToken, System.Threading.Tasks.Task<TDestination>> transformFactory, System.IObservable<System.Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TKey>> TransformImmutable<TDestination, TSource, TKey>(this System.IObservable<DynamicData.IChangeSet<TSource, TKey>> source, System.Func<TSource, TDestination> transformFactory)
where TDestination : notnull
where TSource : notnull
Expand Down Expand Up @@ -2108,6 +2116,14 @@ namespace DynamicData
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this System.IObservable<DynamicData.IChangeSet<TSource, TKey>> source, System.Func<TSource, DynamicData.Kernel.Optional<TSource>, TKey, System.Threading.CancellationToken, System.Threading.Tasks.Task<TDestination>> transformFactory, System.Action<DynamicData.Kernel.Error<TSource, TKey>> errorHandler, DynamicData.TransformAsyncOptions options)
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this System.IObservable<DynamicData.IChangeSet<TSource, TKey>> source, System.Func<TSource, DynamicData.Kernel.Optional<TSource>, TKey, System.Threading.CancellationToken, System.Threading.Tasks.Task<TDestination>> transformFactory, System.Action<DynamicData.Kernel.Error<TSource, TKey>> errorHandler, System.IObservable<System.Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull { }
public static System.IObservable<DynamicData.IChangeSet<DynamicData.Node<TObject, TKey>, TKey>> TransformToTree<TObject, TKey>(this System.IObservable<DynamicData.IChangeSet<TObject, TKey>> source, System.Func<TObject, TKey> pivotOn, System.IObservable<System.Func<DynamicData.Node<TObject, TKey>, bool>>? predicateChanged = null)
where TObject : class
where TKey : notnull { }
Expand Down Expand Up @@ -2481,6 +2497,9 @@ namespace DynamicData
public static System.IObservable<DynamicData.IChangeSet<TDestination>> TransformAsync<TSource, TDestination>(this System.IObservable<DynamicData.IChangeSet<TSource>> source, System.Func<TSource, DynamicData.Kernel.Optional<TDestination>, int, System.Threading.Tasks.Task<TDestination>> transformFactory, bool transformOnRefresh = false)
where TSource : notnull
where TDestination : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination>> TransformAsync<TSource, TDestination>(this System.IObservable<DynamicData.IChangeSet<TSource>> source, System.Func<TSource, DynamicData.Kernel.Optional<TDestination>, int, System.Threading.CancellationToken, System.Threading.Tasks.Task<TDestination>> transformFactory, bool transformOnRefresh = false)
where TSource : notnull
where TDestination : notnull { }
public static System.IObservable<DynamicData.IChangeSet<TDestination>> TransformMany<TDestination, TSource>(this System.IObservable<DynamicData.IChangeSet<TSource>> source, System.Func<TSource, DynamicData.IObservableList<TDestination>> manySelector, System.Collections.Generic.IEqualityComparer<TDestination>? equalityComparer = null)
where TDestination : notnull
where TSource : notnull { }
Expand Down Expand Up @@ -3100,4 +3119,4 @@ namespace DynamicData.Tests
public void Dispose() { }
protected virtual void Dispose(bool isDisposing) { }
}
}
}
10 changes: 5 additions & 5 deletions src/DynamicData/Cache/Internal/TransformAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace DynamicData.Cache.Internal;

internal class TransformAsync<TDestination, TSource, TKey>(
IObservable<IChangeSet<TSource, TKey>> source,
Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory,
Func<TSource, Optional<TSource>, TKey, CancellationToken, Task<TDestination>> transformFactory,
Action<Error<TSource, TKey>>? exceptionCallback,
IObservable<Func<TSource, TKey, bool>>? forceTransform = null,
int? maximumConcurrency = null,
Expand Down Expand Up @@ -45,7 +45,7 @@ private IObservable<IChangeSet<TDestination, TKey>> DoTransform(ChangeAwareCache
var toTransform = cache.KeyValues.Where(kvp => shouldTransform(kvp.Value.Source, kvp.Key)).Select(kvp =>
new Change<TSource, TKey>(ChangeReason.Update, kvp.Key, kvp.Value.Source, kvp.Value.Source)).ToArray();

return toTransform.Select(change => Observable.Defer(() => Transform(change).ToObservable()))
return toTransform.Select(change => Observable.FromAsync(t => Transform(change, t)))
.Merge(maximumConcurrency ?? int.MaxValue)
.ToArray()
.Select(transformed => ProcessUpdates(cache, transformed));
Expand All @@ -54,7 +54,7 @@ private IObservable<IChangeSet<TDestination, TKey>> DoTransform(ChangeAwareCache
private IObservable<IChangeSet<TDestination, TKey>> DoTransform(
ChangeAwareCache<TransformedItemContainer, TKey> cache, IChangeSet<TSource, TKey> changes)
{
return changes.Select(change => Observable.FromAsync(() => Transform(change)))
return changes.Select(change => Observable.FromAsync(t => Transform(change, t)))
.Merge(maximumConcurrency ?? int.MaxValue)
.ToArray()
.Select(transformed => ProcessUpdates(cache, transformed));
Expand Down Expand Up @@ -105,13 +105,13 @@ private ChangeSet<TDestination, TKey> ProcessUpdates(ChangeAwareCache<Transforme
return new ChangeSet<TDestination, TKey>(transformed);
}

private async Task<TransformResult> Transform(Change<TSource, TKey> change)
private async Task<TransformResult> Transform(Change<TSource, TKey> change, CancellationToken cancellationToken)
{
try
{
if (change.Reason is ChangeReason.Add or ChangeReason.Update || (change.Reason is ChangeReason.Refresh && transformOnRefresh))
{
var destination = await transformFactory(change.Current, change.Previous, change.Key)
var destination = await transformFactory(change.Current, change.Previous, change.Key, cancellationToken)
.ConfigureAwait(false);
return new TransformResult(change, new TransformedItemContainer(change.Current, destination));
}
Expand Down
32 changes: 30 additions & 2 deletions src/DynamicData/Cache/ObservableCacheEx.TransformAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestin
return source.TransformAsync((current, _, key) => transformFactory(current, key), forceTransform);
}

/// <inheritdoc cref="TransformAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, Task{TDestination}}, IObservable{Func{TSource, TKey, bool}}?)"/>
/// <remarks>This overload takes a factory that receives the current item the previous item and key.</remarks>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));

return source.TransformAsync((current, previous, key, _) => transformFactory(current, previous, key), forceTransform);
}

/// <summary>
/// Async version of <see cref="Transform{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, TDestination}, IObservable{Func{TSource, TKey, bool}}?)"/>.
/// Projects each item using an async factory that returns <see cref="Task{TResult}"/>.
Expand Down Expand Up @@ -88,7 +102,7 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestin
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="transformFactory"/> is <see langword="null"/>.</exception>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, CancellationToken, Task<TDestination>> transformFactory, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
Expand Down Expand Up @@ -124,7 +138,7 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestin
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));

return source.TransformAsync((current, _, key) => transformFactory(current, key), options);
return TransformAsync(source, (current, _, key, _) => transformFactory(current, key), options);
}

/// <inheritdoc cref="TransformAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, Task{TDestination}}, IObservable{Func{TSource, TKey, bool}}?)"/>
Expand All @@ -138,6 +152,20 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestin
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));

return TransformAsync(source, (current, previous, key, _) => transformFactory(current, previous, key), options);
}

/// <inheritdoc cref="TransformAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, CancellationToken, Task{TDestination}}, IObservable{Func{TSource, TKey, bool}}?)"/>
/// <remarks>This overload accepts <see cref="TransformAsyncOptions"/> to control concurrency and Refresh handling.</remarks>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, CancellationToken, Task<TDestination>> transformFactory, TransformAsyncOptions options)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));

return new TransformAsync<TDestination, TSource, TKey>(source, transformFactory, null, null, options.MaximumConcurrency, options.TransformOnRefresh).Run();
}
}
31 changes: 28 additions & 3 deletions src/DynamicData/Cache/ObservableCacheEx.TransformSafeAsync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDe
return source.TransformSafeAsync((current, _, key) => transformFactory(current, key), errorHandler, forceTransform);
}

/// <inheritdoc cref="TransformSafeAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, CancellationToken, Task{TDestination}}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory, Action<Error<TSource, TKey>> errorHandler, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));
errorHandler.ThrowArgumentNullExceptionIfNull(nameof(errorHandler));

return source.TransformSafeAsync((s, p, k, t) => transformFactory(s, p, k), errorHandler, forceTransform);
}

/// <summary>
/// Async version of <see cref="TransformSafe{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, TDestination}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>.
/// Projects each item using an async factory, catching factory exceptions via a mandatory error handler.
Expand All @@ -63,14 +77,14 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDe
/// <typeparam name="TSource">The type of the source items.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <param name="source">The source <see cref="IObservable{IChangeSet{TSource, TKey}}"/> to transform asynchronously with error handling.</param>
/// <param name="transformFactory">The <see cref="Func{TSource, Optional{TSource}, TKey, Task{TDestination}}"/> async function that produces a <typeparamref name="TDestination"/>.</param>
/// <param name="transformFactory">The <see cref="Func{TSource, Optional{TSource}, TKey, CancellationToken, Task{TDestination}}"/> async function that produces a <typeparamref name="TDestination"/>.</param>
/// <param name="errorHandler">A <see cref="Action{T}"/> that called when <paramref name="transformFactory"/> throws or faults. The item is skipped and the stream continues.</param>
/// <param name="forceTransform">An optional <see cref="IObservable{T}"/> that forces re-transformation of matching items.</param>
/// <returns>An observable changeset of transformed items.</returns>
/// <remarks>Combines the async execution model of <see cref="TransformAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, Task{TDestination}}, IObservable{Func{TSource, TKey, bool}}?)"/> with the error-safe behavior of <see cref="TransformSafe{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, TDestination}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>.</remarks>
/// <exception cref="ArgumentNullException"><paramref name="source"/>, <paramref name="transformFactory"/>, or <paramref name="errorHandler"/> is <see langword="null"/>.</exception>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory, Action<Error<TSource, TKey>> errorHandler, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, CancellationToken, Task<TDestination>> transformFactory, Action<Error<TSource, TKey>> errorHandler, IObservable<Func<TSource, TKey, bool>>? forceTransform = null)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
Expand Down Expand Up @@ -112,13 +126,24 @@ public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDe
return source.TransformSafeAsync((current, _, key) => transformFactory(current, key), errorHandler, options);
}

/// <inheritdoc cref="TransformSafeAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, Task{TDestination}}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>
/// <inheritdoc cref="TransformSafeAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, CancellationToken, Task{TDestination}}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>
/// <remarks>This overload accepts <see cref="TransformAsyncOptions"/> to control concurrency and Refresh handling.</remarks>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, Task<TDestination>> transformFactory, Action<Error<TSource, TKey>> errorHandler, TransformAsyncOptions options)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
{
return source.TransformSafeAsync((current, previous, key, cancel) => transformFactory(current, previous, key), errorHandler, options);
}

/// <inheritdoc cref="TransformSafeAsync{TDestination, TSource, TKey}(IObservable{IChangeSet{TSource, TKey}}, Func{TSource, Optional{TSource}, TKey, CancellationToken, Task{TDestination}}, Action{Error{TSource, TKey}}, IObservable{Func{TSource, TKey, bool}}?)"/>
/// <remarks>This overload accepts <see cref="TransformAsyncOptions"/> to control concurrency and Refresh handling.</remarks>
[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "By Design.")]
public static IObservable<IChangeSet<TDestination, TKey>> TransformSafeAsync<TDestination, TSource, TKey>(this IObservable<IChangeSet<TSource, TKey>> source, Func<TSource, Optional<TSource>, TKey, CancellationToken, Task<TDestination>> transformFactory, Action<Error<TSource, TKey>> errorHandler, TransformAsyncOptions options)
where TDestination : notnull
where TSource : notnull
where TKey : notnull
{
source.ThrowArgumentNullExceptionIfNull(nameof(source));
transformFactory.ThrowArgumentNullExceptionIfNull(nameof(transformFactory));
Expand Down
Loading
Loading