[英]Building an Observable Repository with Rx
I'm working in the common scenario whereby I'd like to access a subset of a repository and not worry about having to keep it updated eg 'get all orders whose price is greater than 10'. 我在常见的情况下工作,我想访问存储库的一个子集,而不用担心必须保持更新,例如“获取价格大于10的所有订单”。 I have implemented a solution but have two issues with it (listed at the end). 我已经实现了一个解决方案,但它有两个问题(最后列出)。
A subset of a repository can be achieved with something equivalent to 可以使用相当于的东西来实现存储库的子集
var expensiveOrders = Repository.GetOrders().Where(o => o.Price > 10);
But this is an IEnumerable
and will not be updated when the original collection is updated. 但这是一个IEnumerable
并且在更新原始集合时不会更新。 I could add handlers for CollectionChanged
, but what if we want to access a further subset? 我可以为CollectionChanged
添加处理程序,但是如果我们想要访问另一个子集呢?
var expensiveOrdersFromBob = expensiveOrders.Where(o => o.Name == Bob);
We'd have to wire up a collection-changed for this one as well. 我们还必须为这个收集更改集合。 The concept of live updates led me to thinking of Rx, so I set about to build an ObservableCache which contains both the ObservableCollection
of items that auto-updates itself, and an RX stream for notification. 实时更新的概念让我想到了Rx,所以我开始构建一个ObservableCache ,它包含自动更新自身的项目的ObservableCollection
和用于通知的RX流。 (The stream is also what updates the cache under the hood.) (流也是更新缓存下的缓存。)
class ObservableCache<T> : IObservableCache<T>
{
private readonly ObservableCollection<T> _cache;
private readonly IObservable<Tuple<T, CRUDOperationType>> _updates;
public ObservableCache(IEnumerable<T> initialCache
, IObservable<Tuple<T, CRUDOperationType>> currentStream, Func<T, bool> filter)
{
_cache = new ObservableCollection<T>(initialCache.Where(filter));
_updates = currentStream.Where(tuple => filter(tuple.Item1));
_updates.Subscribe(ProcessUpdate);
}
private void ProcessUpdate(Tuple<T, CRUDOperationType> update)
{
var item = update.Item1;
lock (_cache)
{
switch (update.Item2)
{
case CRUDOperationType.Create:
_cache.Add(item);
break;
case CRUDOperationType.Delete:
_cache.Remove(item);
break;
case CRUDOperationType.Replace:
case CRUDOperationType.Update:
_cache.Remove(item); // ToDo: implement some key-based equality
_cache.Add(item);
break;
}
}
}
public ObservableCollection<T> Cache
{
get { return _cache; }
}
public IObservable<T> Updates
{
get { return _updates.Select(tuple => tuple.Item1); }
}
public IObservableCache<T> Where(Func<T, bool> predicate)
{
return new ObservableCache<T>(_cache, _updates, predicate);
}
}
You can then use it like this: 然后你可以像这样使用它:
var expensiveOrders = new ObservableCache<Order>(_orders
, updateStream
, o => o.Price > 10);
expensiveOrders.Updates.Subscribe
(o => Console.WriteLine("Got new expensive order: " + o));
_observableBoundToSomeCtrl = expensiveOrders.Cache;
var expensiveOrdersFromBob = expensiveOrders
.Where(o => o.Name == "Bob");
expensiveOrdersFromBob.Updates.Subscribe
(o => Console.WriteLine("Got new expensive order from Bob: " + o));
_observableBoundToSomeOtherCtrl = expensiveOrdersFromBob.Cache;
And so forth, the idea being that you can keep projecting the cache into narrower and narrower subsets and never have to worry about it being out of sync. 等等,这个想法是你可以继续将缓存投射到更窄更窄的子集中,而不必担心它不同步。 So what is my problem then? 那么我的问题是什么呢?
ISequenceableItem
interface. 我想我需要某种排序,但这意味着让我所有的T对象实现一个ISequenceableItem
接口。 Is there any better way to do this? 有没有更好的方法来做到这一点? RX is great because it handles all the threading for you. RX非常棒,因为它可以为您处理所有线程。 I'd like to leverage that. 我想利用它。 http://github.com/wasabii/OLinq上的OLinq项目是为这种反应性更新而设计的,我认为ObservableView就是你所追求的。
Have a look at these two projects which achieve what you want albeit by different means: 看看这两个项目,虽然通过不同的方式实现了你想要的东西:
https://github.com/RolandPheasant/DynamicData https://github.com/RolandPheasant/DynamicData
https://bitbucket.org/mendelmonteiro/reactivetables [disclaimer: this is my project] https://bitbucket.org/mendelmonteiro/reactivetables [免责声明:这是我的项目]
Suppose you have a definition like this: 假设你有这样的定义:
class SetOp<T>
{
public T Value { get; private set; }
public bool Include { get; private set; }
public SetOp(T value, bool include)
{
Value = value;
Include = include;
}
}
Using Observable.Scan
and System.Collections.Immutable
you can do something like this: 使用Observable.Scan
和System.Collections.Immutable
你可以这样做:
IObservable<SetOp<int>> ops = ...;
IImmutableSet<int> empty = ImmutableSortedSet<int>.Empty;
var observableSet = ops
.Scan(empty, (s, op) => op.Include ? s.Add(op.Value) : s.Remove(op.Value))
.StartWith(empty);
Using the immutable collection type is the key trick here: any observer of the observableSet
can do whatever it wants with the values that are pushed at it, because they are immutable. 使用不可变集合类型是这里的关键技巧: observableSet
任何观察者都可以使用推送它的值执行任何操作,因为它们是不可变的。 Add it is efficient because it reuses the majority of the set data structure between consecutive values. 添加它是有效的,因为它重用连续值之间的大部分设置数据结构。
Here is an example of an ops
stream and the corresponding observableSet
. 以下是ops
流和相应的observableSet
的示例。
ops observableSet
-------- ------------------
{}
Add 7 {7}
Add 4 {4,7}
Add 5 {4,5,7}
Add 6 {4,5,6,7}
Remove 5 {4,6,7}
Add 8 {4,6,7,8}
Remove 4 {6,7,8}
You should not need to lock _cache
within ProcessUpdate
. 你不应该需要锁定_cache
内ProcessUpdate
。 If your source observable currentStream
is honoring Rx Guidelines you are guaranteed to only be within a single call to OnNext
at a time. 如果您的源可观察currentStream
符合Rx指南,则保证您一次只能在一次OnNext
调用中。 In otherwords, you will not receive another value from the stream while you are still processing the previous value. 换句话说,当您仍在处理之前的值时,您将不会从流中收到其他值。
The only reliable way to solve your race condition is to make sure you create the cache before the updateStream
starts producing data. 解决竞争条件的唯一可靠方法是确保在updateStream
开始生成数据之前创建缓存。
You may want to take a look at Extensions for Reactive Extensions (Rxx) . 您可能需要查看Retensions for Reactive Extensions(Rxx) 。 I believe Dave has built a number of utilities for binding UI controls to observable data. 我相信Dave已经构建了许多用于将UI控件绑定到可观察数据的实用程序。 Documentation is sparse. 文档很少。 I don't know if there is anything there for what you are doing. 我不知道你在做什么。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.