Merging multiple observables and updating existing subscribers? - c#

How do I aggregate hot observables which may or may not have subscribers into a new observable and continue to provide all new data to existing subscribers?
As an example, imagine we have some class like this:
class SomeClass
{
IObservable<string> Actions { get; set; } = Observable.Empty<string>();
void AddActionCreator(IObservable<string> creator)
{
Actions = Actions.Merge(creator);
}
}
The problem I am running into is if AddActionCreator adds a new stream of actions then any previous subscribers of SomeClass.Actions which subscribed before that new stream is merged will never get the new actions.

It's easy to do what you want. What you need here is a SelectMany and a Subject<IObservabe<string>>.
Here's the class you need:
public class SomeClass
{
private Subject<IObservable<string>> _sources = new Subject<System.IObservable<string>>();
public IObservable<string> Actions { get; private set; } = null;
public SomeClass()
{
this.Actions = _sources.SelectMany(x => x);
}
public void AddActionCreator(IObservable<string> creator)
{
_sources.OnNext(creator);
}
}
Now you can use it like this:
var sc = new SomeClass();
sc.Actions.Subscribe(x => Console.WriteLine($"1:{x}"));
sc.AddActionCreator(Observable.Return("Hello"));
sc.Actions.Subscribe(x => Console.WriteLine($"2:{x}"));
sc.AddActionCreator(Observable.Range(0, 3).Select(x => $"{x}"));
sc.Actions.Subscribe(x => Console.WriteLine($"3:{x}"));
sc.AddActionCreator(Observable.Return("World"));
You'll get this output:
1:Hello
1:0
1:1
1:2
2:0
2:1
2:2
1:World
2:World
3:World
You can see that the new observables are added to the existing subscribers.

Related

Getting Issue with Source Cache in ReactiveUI

I have one issue in subscribing to source cache. Let me describe the problem.
Lets say I have Test Class
public class Test {
public bool feature1 {get; set;} = false;
public bool feature2 {get; set; } = false;
public string name;
public Test(string name){
this.name = name
}
}
I want to see the changes happening in the property of test class and subscriber react according to change. But with current Implementation getting notification only when source is getting updated with new data not if any property of element in source cache is getting updated.
class Notifier {
public SourceCache<Test, string> testClassNotifier = new SourceCache<Test, string>(x => x.Name);
public Notifier(){
Task.Run(() =>
{
this.AddOrUpdateSourceCache();
this.SubscribeTestObj1();
this.SubscribeTestObj2();
}).ConfigureAwait(false);
}
private AddOrUpdateSourceCache()
{
List<Test> testListObj = new List<Test>() { new Test("test1"), new Test("test2") };
for (Test obj : testListObj) {
this.testClassNotifier.AddOrUpdate(obj);
}
Task.Run(async () => {
for(int i = 0; i<2; i++) {
this.testListObj[i].feature1 = true;
await Task.Delay(4000).ConfigureAwait(false);
// I want here to my get the notification in change with intial values as well.
}
}).ConfiguareAwait(false);
}
private IObservable<Test,string> GetNotification(string name){
// which api should use here ?? Or any way I can use `WhenAny` here.
return this.testClassNotifier.Watch(name);
}
private SubscribeTestObj1() {
this.GetNotification("test1").Subscribe(obj => // do something);
}
private SubscribeTestObj1() {
this.GetNotification("test2").Subscribe(obj => // do something);
}
}
One solution: implement INotifyPropertyChanged on the Test class, and use AutoRefresh()
Example:
public class Test : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
bool _feature1 = false;
public bool feature1
{
get => _feature1;
set
{
_feature1 = value;
PropertyChanged?.Invoke(this, new(nameof(feature1)));
}
}
// ... see the rest of the class in OP's question
}
Test:
var source = new SourceCache<Test, string>(x => x.name);
var a = new Test("a");
var b = new Test("b");
source
.Connect()
.AutoRefresh()
.Watch(b.name)
.Subscribe(change => Console.WriteLine($"Reason: <{change.Reason}> feature1: <{change.Current.feature1}>"));
source.AddOrUpdate(a);
source.AddOrUpdate(b);
b.feature1 = true;
Output:
Reason: <Add> feature1: <False>
Reason: <Refresh> feature1: <True>

Async method - weird behavior: my app is getting stuck

I have a weird problem with my async method. I made second project in my solution in VS to connect my app to an API (using RestSharp for it). I made dependencies etc.
The problem is when I call this method from the UI by clicking a button (it only start backend code, there is no relation to UI etc.) app getting stuck. There is no errors, the only things I can see in the output window are "The thread ****** has exited with code 0 (0x0)." and it's going infinitely.
I took that code (only from project responsible for connecting and taking data from an api) and made a new solution, new project, but exacly copied code and it is working fine.
This is method what I am calling in the "main" WPF app using ICommand etc:
private void Api()
{
_orderService = new OrderService();
}
And those are classes in API project:
BLContext.cs
public class BLContext
{
private RestClient _client;
public RestClient Client { get; set; }
private string _token;
public string Token { get; set; }
public BLContext()
{
Client = new RestClient("https://api.baselinker.com/connector.php");
Token = "************************";
}
}
BaseAPIRepository.cs
public class BaseAPIRepository
{
private BLContext _bl = new BLContext();
RestRequest Request = new RestRequest();
public BaseAPIRepository() { }
public async Task<List<Order>> GetOrders()
{
List<Order> orders = new List<Order>();
List<JToken> orderList = new List<JToken>();
StartRequest("getOrders");
Request.AddParameter("parameters", "{ \"status_id\": 13595 }");
Request.AddParameter("parameters", "{ \"get_unconfirmed_orders\": false }");
RestResponse restResponse = await _bl.Client.PostAsync(Request);
JObject response = (JObject)JsonConvert.DeserializeObject(restResponse.Content);
orderList = response["orders"].ToList();
foreach (JToken order in orderList)
{
Order newOrder = new Order();
newOrder.Id = (int)order["order_id"];
newOrder.ProductsInOrder = GetProductsFromOrder((JArray)order["products"]);
orders.Add(newOrder);
}
return orders;
}
public void StartRequest(string method)
{
Request.AddParameter("token", _bl.Token);
Request.AddParameter("method", method);
}
public List<OrderedProduct> GetProductsFromOrder(JArray productsInOrder)
{
List<OrderedProduct> tmpListOfProducts = new List<OrderedProduct>();
foreach (var item in productsInOrder)
{
OrderedProduct tmpOrderedProduct = new OrderedProduct();
//tmpOrderedProduct.Id = (int)item["product_id"];
tmpOrderedProduct.Signature = (string)item["sku"];
tmpOrderedProduct.Quantity = (int)item["quantity"];
tmpListOfProducts.Add(tmpOrderedProduct);
}
return tmpListOfProducts;
}
}
OrderService.cs
public class OrderService
{
private BaseAPIRepository _repo;
private List<Order> _ordersList;
public List<Order> OrdersList { get; set; }
public OrderService()
{
_repo = new BaseAPIRepository();
OrdersList = new List<Order>();
OrdersList = _repo.GetOrders().Result;
Console.WriteLine("Test line to see if it passed 24th line.");
}
}
App is getting stuck on line:
RestResponse restResponse = await _bl.Client.PostAsync(Request);
The core problem - as others have noted - is that your code is blocking on asynchronous code, which you shouldn't do (as I explain on my blog). This is particularly true for UI apps, which deliver a bad user experience when the UI thread is blocked. So, even if the code wasn't deadlocking, it wouldn't be a good idea to block on the asynchronous code anyway.
There are certain places in a UI app where the code simply cannot block if you want a good user experience. View and ViewModel construction are two of those places. When a VM is being created, the OS is asking your app to display its UI right now, and waiting for a network request before displaying data is just a bad experience.
Instead, your application should initialize and return its UI immediately (synchronously), and display that. If you have to do a network request to get some data to display, it's normal to synchronously initialize the UI into a "loading" state, start the network request, and then at that point the construction/initialization is done. Later, when the network request completes, the UI is updated into a "loaded" state.
If you want to take this approach, there's a NotifyTask<T> type in my Nito.Mvvm.Async package which may help. Its design is described in this article and usage looks something like this (assuming OrderService is actually a ViewModel):
public class OrderService
{
private BaseAPIRepository _repo;
public NotifyTask<List<Order>> OrdersList { get; set; }
public OrderService()
{
_repo = new BaseAPIRepository();
OrdersList = NotifyTask.Create(() => _repo.GetOrders());
}
}
Then, instead of data-binding to OrderService.OrdersList, you can data-bind to OrderService.OrdersList.Result, OrderService.OrdersList.IsCompleted, etc.
You should never call Task.Result on an incomplete Task to avoid deadlocking the application. Always await a Task.
C# doesn't allow async constructors. Constructors are meant to return fast after some brief initialization. They are not a place for long-running operations or starting background threads (even if async constructors were allowed).
There are a few solutions to avoid the requirement of async constructors.
A simple alternative solution using Lazy<T> or AsyncLazy<T> (requires to install the Microsoft.VisualStudio.Threading package via the NuGet Package Manager). Lazy<T> allows to defer the instantiation or allocation of expensive resources.
public class OrderService
{
public List<object> Orders => this.OrdersInitializer.GetValue();
private AsyncLazy<List<object>> OrdersInitializer { get; }
public OrderService()
=> this.OrdersInitializer = new AsyncLazy<List<object>>(InitializeOrdersAsync, new JoinableTaskFactory(new JoinableTaskContext()));
private async Task<List<object>> InitializeOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
}
public static void Main()
{
var orderService = new OrderService();
// Trigger async initialization
orderService.Orders.Add(4);
}
You can expose the data using a method instead of a property
public class OrderService
{
private List<object> Orders { get; set; }
public async Task<List<object>> GetOrdersAsync()
{
if (this.Orders == null)
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
}
return this.Orders;
}
}
public static async Task Main()
{
var orderService = new OrderService();
// Trigger async initialization
List<object> orders = await orderService.GetOrdersAsync();
}
Use an InitializeAsync method that must be called before using the instance
public class OrderService
{
private List<object> orders;
public List<object> Orders
{
get
{
if (!this.IsInitialized)
{
throw new InvalidOperationException();
}
return this.orders;
}
private set
{
this.orders = value;
}
}
public bool IsInitialized { get; private set; }
public async Task<List<object>> InitializeAsync()
{
if (this.IsInitialized)
{
return;
}
await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
this.IsInitialized = true;
}
}
public static async Task Main()
{
var orderService = new OrderService();
// Trigger async initialization
await orderService.InitializeAsync();
}
Instantiate the instance by passing the expensive arguments to the constructor
public class OrderService
{
public List<object> Orders { get; }
public async Task<List<object>> OrderService(List<object> orders)
=> this.Orders = orders;
}
public static async Task Main()
{
List<object> orders = await GetOrdersAsync();
// Instantiate with the result of the async operation
var orderService = new OrderService(orders);
}
private static async Task<List<object>> GetOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
Use a factory method and a private constructor
public class OrderService
{
public List<object> Orders { get; set; }
private OrderServiceBase()
=> this.Orders = new List<object>();
public static async Task<OrderService> CreateInstanceAsync()
{
var instance = new OrderService();
await Task.Delay(TimeSpan.FromSeconds(5));
instance.Orders = new List<object> { 1, 2, 3 };
return instance;
}
}
public static async Task Main()
{
// Trigger async initialization
OrderService orderService = await OrderService.CreateInstanceAsync();
}

Reactive services. Grouping and caching streams

New: Entire source code with tests is now at https://github.com/bboyle1234/ReactiveTest
Let's imagine we have a view state object that is able to be updated by small partial view change events. Here are some example models of the total view, the incremental view update events and the accumulator function Update that builds the total view:
interface IDeviceView : ICloneable {
Guid DeviceId { get; }
}
class DeviceTotalView : IDeviceView {
public Guid DeviceId { get; set; }
public int Voltage { get; set; }
public int Currents { get; set; }
public object Clone() => this.MemberwiseClone();
}
class DeviceVoltagesUpdateView : IDeviceView {
public Guid DeviceId { get; set; }
public int Voltage { get; set; }
public object Clone() => this.MemberwiseClone();
}
class DeviceCurrentsUpdateView : IDeviceView {
public Guid DeviceId { get; set; }
public int Current { get; set; }
public object Clone() => this.MemberwiseClone();
}
class DeviceUpdateEvent {
public DeviceTotalView View;
public IDeviceView LastUpdate;
}
static DeviceUpdateEvent Update(DeviceUpdateEvent previousUpdate, IDeviceView update) {
if (update.DeviceId != previousUpdate.View.DeviceId) throw new InvalidOperationException("Device ids do not match (numskull exception).");
var view = (DeviceTotalView)previousUpdate.View.Clone();
switch (update) {
case DeviceVoltagesUpdateView x: {
view.Voltage = x.Voltage;
break;
}
case DeviceCurrentsUpdateView x: {
view.Currents = x.Current;
break;
}
}
return new DeviceUpdateEvent { View = view, LastUpdate = update };
}
Next, let's imagine we already have an injectable service that is able to produce an observable stream of the small update events for all devices, and that we want to create a service that can produce an aggregated view stream for individual devices.
Here is the interface of the service we want to create:
interface IDeviceService {
/// <summary>
/// Gets an observable that produces aggregated update events for the device with the given deviceId.
/// On subscription, the most recent event is immediately pushed to the subscriber.
/// There can be multiple subscribers.
/// </summary>
IObservable<DeviceUpdateEvent> GetDeviceStream(Guid deviceId);
}
How can I implement this interface and its requirements using the reactive extensions in the System.Reactive v4 library, targeting .netstandard2.0? Here's my boiler code with comments and that's as far as I've been able to get.
class DeviceService : IDeviceService {
readonly IObservable<IDeviceView> Source;
public DeviceService(IObservable<IDeviceView> source) { // injected parameter
/// When injected here, "source" is cold in the sense that it won't produce events until the first time it is subscribed.
/// "source" will throw an exception if its "Subscribe" method is called more than once as it is intended to have only one observer and
/// be read all the way from the beginning.
Source = source;
/// Callers of the "Subscribe" method below will expect data to be preloaded and will expect to be immediately delivered the most
/// recent event. So we need to immediately subscribe to "source" and start preloading the aggregate streams.
/// I'm assuming there is going to need to be a groupby to split the stream by device id.
var groups = source.GroupBy(x => x.DeviceId);
/// Now somehow we need to perform the aggregrate function on each grouping.
/// And create an observable that immediately delivers the most recent aggregated event when "Subscribe" is called below.
}
public IObservable<DeviceUpdateEvent> GetDeviceStream(Guid deviceId) {
/// How do we implement this? The observable that we return must be pre-loaded with the latest update
throw new NotImplementedException();
}
}
You have some weird code in that gist. Here's what I got working:
public class DeviceService : IDeviceService, IDisposable
{
readonly IObservable<IDeviceView> Source;
private readonly Dictionary<Guid, IObservable<DeviceUpdateEvent>> _updateStreams = new Dictionary<Guid, IObservable<DeviceUpdateEvent>>();
private readonly IObservable<(Guid, IObservable<DeviceUpdateEvent>)> _groupedStream;
private readonly CompositeDisposable _disposable = new CompositeDisposable();
public DeviceService(IObservable<IDeviceView> source)
{
Source = source;
_groupedStream = source
.GroupBy(v => v.DeviceId)
.Select(o => (o.Key, o
.Scan(new DeviceUpdateEvent { View = DeviceTotalView.GetInitialView(o.Key), LastUpdate = null }, (lastTotalView, newView) => lastTotalView.Update(newView))
.Replay(1)
.RefCount()
));
var groupSubscription = _groupedStream.Subscribe(t =>
{
_updateStreams[t.Item1] = t.Item2;
_disposable.Add(t.Item2.Subscribe());
});
_disposable.Add(groupSubscription);
}
public void Dispose()
{
_disposable.Dispose();
}
public IObservable<DeviceUpdateEvent> GetDeviceStream(Guid deviceId)
{
/// How do we implement this? The observable that we return must be pre-loaded with the latest update
if(this._updateStreams.ContainsKey(deviceId))
return this._updateStreams[deviceId];
return _groupedStream
.Where(t => t.Item1 == deviceId)
.Select(t => t.Item2)
.Switch();
}
}
The meat here is the _groupedStream piece. You group by DeviceId, as you said, then you use Scan to update state. I also moved Update to a static class and made it an extension method. You'll need an initial state, so I modified your DeviceTotalView class to get that. Modify accordingly:
public class DeviceTotalView : IDeviceView
{
public Guid DeviceId { get; set; }
public int Voltage { get; set; }
public int Currents { get; set; }
public object Clone() => this.MemberwiseClone();
public static DeviceTotalView GetInitialView(Guid deviceId)
{
return new DeviceTotalView
{
DeviceId = deviceId,
Voltage = 0,
Currents = 0
};
}
}
Next, the .Replay(1).Refcount() serves to remember the most recent update then provide that on subscription. We then stuff all of these child observables into a dictionary for easy retrieval on the method call. The dummy subscriptions (_disposable.Add(t.Item2.Subscribe())) are necessary for Replay to work.
In the event that there's an early request for a DeviceId that doesn't yet have an update, we subscribe to the _groupedStream which will wait for the first update, producing that Id's observable, then .Switch subscribes to that child observable.
However, all of this failed against your test code, I'm guessing because of the ConnectableObservableForAsyncProducerConsumerQueue class. I didn't want to debug that, because I wouldn't recommend doing something like that. In general it's not recommended to mix TPL and Rx code. They problems they solve largely overlap and they get in each other's way. So I modified your test code replacing that connectable observable queue thing with a Replay subject.
I also added the test-case for an early request (before an updates for that Device have arrived):
DeviceUpdateEvent deviceView1 = null;
DeviceUpdateEvent deviceView2 = null;
DeviceUpdateEvent deviceView3 = null;
var subject = new ReplaySubject<IDeviceView>();
var id1 = Guid.NewGuid();
var id2 = Guid.NewGuid();
var id3 = Guid.NewGuid();
subject.OnNext(new DeviceVoltagesUpdateView { DeviceId = id1, Voltage = 1 });
subject.OnNext(new DeviceVoltagesUpdateView { DeviceId = id1, Voltage = 2 });
subject.OnNext(new DeviceVoltagesUpdateView { DeviceId = id2, Voltage = 100 });
var service = new DeviceService(subject);
service.GetDeviceStream(id1).Subscribe(x => deviceView1 = x);
service.GetDeviceStream(id2).Subscribe(x => deviceView2 = x);
service.GetDeviceStream(id3).Subscribe(x => deviceView3 = x);
/// I believe there is no need to pause here because the Subscribe method calls above
/// block until the events have all been pushed into the subscribers above.
Assert.AreEqual(deviceView1.View.DeviceId, id1);
Assert.AreEqual(deviceView2.View.DeviceId, id2);
Assert.AreEqual(deviceView1.View.Voltage, 2);
Assert.AreEqual(deviceView2.View.Voltage, 100);
Assert.IsNull(deviceView3);
subject.OnNext(new DeviceVoltagesUpdateView { DeviceId = id2, Voltage = 101 });
Assert.AreEqual(deviceView2.View.Voltage, 101);
subject.OnNext(new DeviceVoltagesUpdateView { DeviceId = id3, Voltage = 101 });
Assert.AreEqual(deviceView3.View.DeviceId, id3);
Assert.AreEqual(deviceView3.View.Voltage, 101);
That passes fine and can be run without async.
Also, as a general tip, I would recommend doing unit tests for Rx code with the Microsoft.Reactive.Testing package, rather than time-gapping things.
A huge thanks to #Shlomo for the answer above.
The implementation given in the accepted answer, whilst a magical education for me, had a couple of issues that also needed to be solved in turn. The first was a threadrace problem, and the second was performance when a large number of devices were in the system. I ended up solving the threadrace AND dramatically improving performance with this modified implementation:
In the constructor, the grouped and scanned device stream is subscribed directly to a BehaviorSubject, which implements the Replay(1).RefCount() functionality required to immediately notify new subscribers of the latest value in the stream.
In the GetDeviceStream method, we continue to use a dictionary lookup to find the device stream, creating a preloaded BehaviorSubject if it doesn't already exist in the dictionary. We have removed the Where search that existed in the previous implementation in the question above. Using the where search caused a threadrace problem that was solved by making the grouped stream replayable. That caused an expontial performance issue. Replacing it with FirstOrDefault reduced the time take by half, and then removing it completely in favor of the GetCreate dictionary technique gave perfect perfomance O(1) instead of O(n2).
GetCreateSubject uses the Lazy proxy object as the dictionary value because the ConcurrentDictionary can sometimes call the Create method more than once for a single key. Supplying a Lazy to the dictionary ensures that the Value property is only called on one of the lazies, and therefore only one BehaviorSubject is created per device.
class DeviceService : IDeviceService, IDisposable {
readonly CompositeDisposable _disposable = new CompositeDisposable();
readonly ConcurrentDictionary<Guid, Lazy<BehaviorSubject<DeviceUpdateEvent>>> _streams = new ConcurrentDictionary<Guid, Lazy<BehaviorSubject<DeviceUpdateEvent>>>();
BehaviorSubject<DeviceUpdateEvent> GetCreateSubject(Guid deviceId) {
return _streams.GetOrAdd(deviceId, Create).Value;
Lazy<BehaviorSubject<DeviceUpdateEvent>> Create(Guid id) {
return new Lazy<BehaviorSubject<DeviceUpdateEvent>>(() => {
var subject = new BehaviorSubject<DeviceUpdateEvent>(DeviceUpdateEvent.GetInitialView(deviceId));
_disposable.Add(subject);
return subject;
});
}
}
public DeviceService(IConnectableObservable<IDeviceView> source) {
_disposable.Add(source
.GroupBy(x => x.DeviceId)
.Subscribe(deviceStream => {
_disposable.Add(deviceStream
.Scan(DeviceUpdateEvent.GetInitialView(deviceStream.Key), DeviceUtils.Update)
.Subscribe(GetCreateSubject(deviceStream.Key)));
}));
_disposable.Add(source.Connect());
}
public void Dispose() {
_disposable.Dispose();
}
public IObservable<DeviceUpdateEvent> GetDeviceStream(Guid deviceId) {
return GetCreateSubject(deviceId).AsObservable();
}
}
[TestMethod]
public async Task Test2() {
var input = new AsyncProducerConsumerQueue<IDeviceView>();
var source = new ConnectableObservableForAsyncProducerConsumerQueue<IDeviceView>(input);
var service = new DeviceService(source);
var ids = Enumerable.Range(0, 100000).Select(i => Guid.NewGuid()).ToArray();
var idsRemaining = ids.ToHashSet();
var t1 = Task.Run(async () => {
foreach (var id in ids) {
await input.EnqueueAsync(new DeviceVoltagesUpdateView { DeviceId = id, Voltage = 1 });
}
});
var t2 = Task.Run(() => {
foreach (var id in ids) {
service.GetDeviceStream(id).Subscribe(x => idsRemaining.Remove(x.View.DeviceId));
}
});
await Task.WhenAll(t1, t2);
var sw = Stopwatch.StartNew();
while (idsRemaining.Count > 0) {
if (sw.Elapsed.TotalSeconds > 600) throw new Exception("Failed");
await Task.Delay(100);
}
}
See entire problem source code and test code here: https://github.com/bboyle1234/ReactiveTest

Triggering DynamicData cache update using Reactive Subject

As a caveat I'm a novice with Rx (2 weeks) and have been experimenting with using Rx, RxUI and Roland Pheasant's DynamicData.
I have a service that initially loads data from local persistence and then, upon some user (or system) instruction will contact the server (TriggerServer in the example) to get additional or replacement data. The solution I've come up with uses a Subject and I've come across many a site discussing the pros/cons of using them. Although I understand the basics of hot/cold it's all based on reading rather than real world.
So, using the below as a simplified version, is this 'right' way of going about this problem or is there something I haven't properly understood somewhere?
NB: I'm not sure how important it is, but the actual code is taken from a Xamarin.Forms app, that uses RxUI, the user input being a ReactiveCommand.
Example:
using DynamicData;
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
public class MyService : IDisposable
{
private CompositeDisposable _cleanup;
private Subject<Unit> _serverSubject = new Subject<Unit>();
public MyService()
{
var data = Initialise().Publish();
AllData = data.AsObservableCache();
_cleanup = new CompositeDisposable(AllData, data.Connect());
}
public IObservableCache<MyData, Guid> AllData { get; }
public void TriggerServer()
{
// This is what I'm not sure about...
_serverSubject.OnNext(Unit.Default);
}
private IObservable<IChangeSet<MyData, Guid>> Initialise()
{
return ObservableChangeSet.Create<MyData, Guid>(async cache =>
{
// inital load - is this okay?
cache.AddOrUpdate(await LoadLocalData());
// is this a valid way of doing this?
var sync = _serverSubject.Select(_ => GetDataFromServer())
.Subscribe(async task =>
{
var data = await task.ConfigureAwait(false);
cache.AddOrUpdate(data);
});
return new CompositeDisposable(sync);
}, d=> d.Id);
}
private IObservable<MyData> LoadLocalData()
{
return Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => new MyData("localdata"));
}
private async Task<MyData> GetDataFromServer()
{
await Task.Delay(2000).ConfigureAwait(true);
return new MyData("serverdata");
}
public void Dispose()
{
_cleanup?.Dispose();
}
}
public class MyData
{
public MyData(string value)
{
Value = value;
}
public Guid Id { get; } = Guid.NewGuid();
public string Value { get; set; }
}
And a simple Console app to run:
public static class TestProgram
{
public static void Main()
{
var service = new MyService();
service.AllData.Connect()
.Bind(out var myData)
.Subscribe(_=> Console.WriteLine("data in"), ()=> Console.WriteLine("COMPLETE"));
while (Continue())
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine($"Triggering Server Call, current data is: {string.Join(", ", myData.Select(x=> x.Value))}");
service.TriggerServer();
}
}
private static bool Continue()
{
Console.WriteLine("Press any key to call server, x to exit");
var key = Console.ReadKey();
return key.Key != ConsoleKey.X;
}
}
Looks very good for first try with Rx
I would suggest few changes:
1) Remove the Initialize() call from the constructor and make it a public method - helps a lot with unit tests and now you can await it if you need to
public static void Main()
{
var service = new MyService();
service.Initialize();
2) Add Throttle to you trigger - this fixes parallel calls to the server returning the same results
3) Don't do anything that can throw in Subscribe, use Do instead:
var sync = _serverSubject
.Throttle(Timespan.FromSeconds(0.5), RxApp.TaskPoolScheduler) // you can pass a scheduler via arguments, or use TestScheduler in unit tests to make time pass faster
.Do(async _ =>
{
var data = await GetDataFromServer().ConfigureAwait(false); // I just think this is more readable, your way was also correct
cache.AddOrUpdate(data);
})
// .Retry(); // or anything alese to handle failures
.Subscribe();
I'm putting what I've come to as my solution just in case there's others that find this while they're wandering the internets.
I ended up removing the Subjects all together and chaining together several SourceCache, so when one changed it pushed into the other and so on. I've removed some code for brevity:
public class MyService : IDisposable
{
private SourceCache<MyData, Guid> _localCache = new SourceCache<MyData, Guid>(x=> x.Id);
private SourceCache<MyData, Guid> _serverCache = new SourceCache<MyData, Guid>(x=> x.Id);
public MyService()
{
var localdata = _localCache.Connect();
var serverdata = _serverCache.Connect();
var alldata = localdata.Merge(serverdata);
AllData = alldata.AsObservableCache();
}
public IObservableCache<MyData, Guid> AllData { get; }
public IObservable<Unit> TriggerLocal()
{
return LoadLocalAsync().ToObservable();
}
public IObservable<Unit> TriggerServer()
{
return LoadServerAsync().ToObservable();
}
}
EDIT: I've changed this again to remove any chaining of caches - I just manage the one cache internally. Lesson is not to post too early.

Moq - Extracting Mock DataSet & Context to a Separate Class

I have a unit test which verifies that a function adds exactly one record to an Entity Framework dataset like so:
var internals = new Mock<DbSet<InternalTransaction>>();
internals.Setup(q => q.Create()).Returns(new InternalTransaction());
var mockDC = new Mock<IDataContext>();
mockDC.Setup(q => q.InternalTransactions).Returns(internals.Object);
// Removed: set up some data for my test
mockDC.Object.DoSomeFunction(data);
internals.Verify(e => e.Add(It.IsAny<InternalTransaction>()), Times.Once());
Since I have a lot of tests to do with this context, I'm trying to extract the mock stuff to a separate class, like so:
public static class DataContext_Creators
{
public static Mock<IDataContext> CreateMockContext()
{
var internals = CreateMockDataSet<InternalTransaction>();
var externals = CreateMockDataSet<ExternalTransaction>();
var mockDC = new Mock<IDataContext>();
mockDC.Setup(q => q.InternalTransactions).Returns(internals.Object);
mockDC.Setup(q => q.ExternalTransactions).Returns(externals.Object);
return mockDC;
}
private static Mock<DbSet<T>> CreateMockDataSet<T>() where T : class, new ()
{
var mockDataSet = new Mock<DbSet<T>>();
mockDataSet.Setup(q => q.Create()).Returns(new T());
// some other stuff, Provider, GetEnumerator, etc
return mockDataSet;
}
}
... and change my test to:
mockContext = Common.DataContext_Creators.CreateMockContext();
context = mockContext.Object;
// Removed: set up some data for my test
context.Object.DoSomeFunction(data);
//internals.Verify(e => e.Add(It.IsAny<InternalTransaction>()), Times.Once());
But now that I can't access "internals" anymore, how can I do the .Verify() statement? context.InternalTransactions won't work because that returns "internals.Object" (which it needs to for the method I'm testing to work).
From that object you may once again regain Mock object of it by using Mock.Get() method. Look at example:
public class Entity
{
public virtual int Id { get; set; }
public virtual int? ParentId { get; set; }
}
static void Main(string[] args)
{
Entity entity = Mock.Of<Entity>();
var mock = Mock.Get<Entity>(entity);
mock.Setup(e => e.ParentId).Returns(11);
var result = entity.ParentId;
// now result == 11
}
You could use Mock.Get method on your internals.Object
Create a new utility class with 2 properties:
Mock<IDataContext> DbContextMock
Mock<DbSet<InternalTransaction>> InternalTransMock
Have your Utility function return this new object so your test has access to both.

Categories

Resources