使用动态数据创建 ViewModel 的派生集合,更新现有项目而不是在源项目更改时创建新项目
Create derrived collection of ViewModels with DynamicData which updates existing item instead of creating a new one on source item change
例如,我有一些指示对象状态的集合的可观察对象(我通过 REST API 定期获取它)。
class User
{
int Id { get; }
string Name { get; }
string Status { get; }
}
IObservable<User> source;
我想创建一个 DynamicCache
对象并在每次 source
给我一个新结果时更新它。所以我写道:
var models = new SourceCache<User,int>(user => user.Id);
models.Connect()
.Transform(u => new UserViewModel() {...})
...
.Bind(out viewModels)
.Subscribe();
source.Subscribe(ul => models.EditDiff(ul, (a, b) => a.Status == b.Status));
但现在每次用户更改其状态时,.Transform(...)
方法都会创建一个 UserViewModel
的新实例,这不是所需的行为。
当具有相同 ID 的源项目发生变化时,我能否以某种方式确定更新现有 ViewModel 属性(在派生集合中)的规则,而不是每次都创建一个新的?
答案是您需要创建自定义运算符。我在这里 TransformWithInlineUpdate 发布了一个要点,您可以将其复制到您的解决方案中。示例用法是:
var users = new SourceCache<User, int>(user => user.Id);
var transformed = users.Connect()
.TransformWithInlineUpdate(u => new UserViewModel(u), (previousViewModel, updatedUser) =>
{
previousViewModel.User = updatedUser;
});
为了回答的完整性,这里是代码:
public static IObservable<IChangeSet<TDestination, TKey>> TransformWithInlineUpdate<TObject, TKey, TDestination>(this IObservable<IChangeSet<TObject, TKey>> source,
Func<TObject, TDestination> transformFactory,
Action<TDestination, TObject> updateAction = null)
{
return source.Scan((ChangeAwareCache<TDestination, TKey>)null, (cache, changes) =>
{
//The change aware cache captures a history of all changes so downstream operators can replay the changes
if (cache == null)
cache = new ChangeAwareCache<TDestination, TKey>(changes.Count);
foreach (var change in changes)
{
switch (change.Reason)
{
case ChangeReason.Add:
cache.AddOrUpdate(transformFactory(change.Current), change.Key);
break;
case ChangeReason.Update:
{
if (updateAction == null) continue;
var previous = cache.Lookup(change.Key)
.ValueOrThrow(()=> new MissingKeyException($"{change.Key} is not found."));
//callback when an update has been received
updateAction(previous, change.Current);
//send a refresh as this will force downstream operators to filter, sort, group etc
cache.Refresh(change.Key);
}
break;
case ChangeReason.Remove:
cache.Remove(change.Key);
break;
case ChangeReason.Refresh:
cache.Refresh(change.Key);
break;
case ChangeReason.Moved:
//Do nothing !
break;
}
}
return cache;
}).Select(cache => cache.CaptureChanges()); //invoke capture changes to return the changeset
}
例如,我有一些指示对象状态的集合的可观察对象(我通过 REST API 定期获取它)。
class User
{
int Id { get; }
string Name { get; }
string Status { get; }
}
IObservable<User> source;
我想创建一个 DynamicCache
对象并在每次 source
给我一个新结果时更新它。所以我写道:
var models = new SourceCache<User,int>(user => user.Id);
models.Connect()
.Transform(u => new UserViewModel() {...})
...
.Bind(out viewModels)
.Subscribe();
source.Subscribe(ul => models.EditDiff(ul, (a, b) => a.Status == b.Status));
但现在每次用户更改其状态时,.Transform(...)
方法都会创建一个 UserViewModel
的新实例,这不是所需的行为。
当具有相同 ID 的源项目发生变化时,我能否以某种方式确定更新现有 ViewModel 属性(在派生集合中)的规则,而不是每次都创建一个新的?
答案是您需要创建自定义运算符。我在这里 TransformWithInlineUpdate 发布了一个要点,您可以将其复制到您的解决方案中。示例用法是:
var users = new SourceCache<User, int>(user => user.Id);
var transformed = users.Connect()
.TransformWithInlineUpdate(u => new UserViewModel(u), (previousViewModel, updatedUser) =>
{
previousViewModel.User = updatedUser;
});
为了回答的完整性,这里是代码:
public static IObservable<IChangeSet<TDestination, TKey>> TransformWithInlineUpdate<TObject, TKey, TDestination>(this IObservable<IChangeSet<TObject, TKey>> source,
Func<TObject, TDestination> transformFactory,
Action<TDestination, TObject> updateAction = null)
{
return source.Scan((ChangeAwareCache<TDestination, TKey>)null, (cache, changes) =>
{
//The change aware cache captures a history of all changes so downstream operators can replay the changes
if (cache == null)
cache = new ChangeAwareCache<TDestination, TKey>(changes.Count);
foreach (var change in changes)
{
switch (change.Reason)
{
case ChangeReason.Add:
cache.AddOrUpdate(transformFactory(change.Current), change.Key);
break;
case ChangeReason.Update:
{
if (updateAction == null) continue;
var previous = cache.Lookup(change.Key)
.ValueOrThrow(()=> new MissingKeyException($"{change.Key} is not found."));
//callback when an update has been received
updateAction(previous, change.Current);
//send a refresh as this will force downstream operators to filter, sort, group etc
cache.Refresh(change.Key);
}
break;
case ChangeReason.Remove:
cache.Remove(change.Key);
break;
case ChangeReason.Refresh:
cache.Refresh(change.Key);
break;
case ChangeReason.Moved:
//Do nothing !
break;
}
}
return cache;
}).Select(cache => cache.CaptureChanges()); //invoke capture changes to return the changeset
}