简体   繁体   English

使用Rx构建一个Observable Repository

[英]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? 那么我的问题是什么呢?

  1. I'm wondering whether I can do away with the CRUD stuff by having RX intrinsically update the collections. 我想知道我是否可以通过让RX本质上更新集合来消除CRUD的东西。 Maybe 'project' the updates with a Select, or something like that? 也许用“选择”或类似的东西“投射”更新?
  2. There is a race condition intrinsic to the repository-with-update pattern, in that I might miss some updates while I'm constructing the new cache. 存储库与更新模式存在内部竞争条件,因为在构建新缓存时我可能会遗漏一些更新。 I think I need some sort of sequencing, but that would mean having all my T objects implement an 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.ScanSystem.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 . 你不应该需要锁定_cacheProcessUpdate 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM