Join an unknown number of sources when all sources contain given key - system.reactive

Given a source provider like below:
IObservable<ISource> Sources();
with each ISource looking like below:
IObservable<IEnumerable<string>> ObserveData(string filter)
I'd like to return:
IObservable<IEnumerable<string>> Results
when a given string is returned from all ISources. Essentially I want the intersection of all the sources.
If a new source is added then everything should re-evaluate.
I'm struggling to come up with a generic solution to this. Most solutions I've seen have a well known number of sources.
Any ideas appreciated.
Answer
Ok after thinking for a while longer I came up with my answer. Possibly it can be improved on but it seems to work for me so I'll post it here for reference in case someone else has a similar issue. Thanks to ibebbs and Shlomo for taking the time to reply, much appreciated.
//Arrange
var s1 = Substitute.For<ISource>();
s1.ObserveData(Arg.Any<string>()).Returns(Observable.Return(new[] { "a", "b", "c", "d" }));
var s2 = Substitute.For<ISource>();
s2.ObserveData(Arg.Any<string>()).Returns(Observable.Return(new[] { "b", "xx", "c", "d" }));
var s3 = Substitute.For<ISource>();
s3.ObserveData(Arg.Any<string>()).Returns(Observable.Return(new[] { "yy", "b", "ff", "d" }));
var expected = new[] { "b", "d" };
var sources = new[] { s1, s2, s3 }.ToObservable();
var scheduler = new TestScheduler();
var observer = scheduler.CreateObserver<IList<string>>();
//Act
sources.Buffer(TimeSpan.FromMilliseconds(500), scheduler)
.Select(s => Observable.CombineLatest(s.Select(x => x.ObserveData("NoFilter"))))
.Switch()
.Select(x =>IntersectAll(x))
.Do(x => Console.WriteLine($"Recieved {string.Join("," , x)}"))
.Subscribe(observer);
scheduler.AdvanceBy(TimeSpan.FromMilliseconds(500).Ticks);
//Assert
observer.Messages.AssertEqual(
OnNext<IList<string>>(0, s => s.SequenceEqual(expected)),
OnCompleted<IList<string>>(0));
For IntersectAll, see Intersection of multiple lists with IEnumerable.Intersect()

Ok, second attempt and I'm pretty sure this is what you need (test fixture included at the bottom):
public interface ISource
{
IObservable<IEnumerable<string>> ObserveData(string filter);
}
public static class ArbitrarySources
{
public static IObservable<IEnumerable<string>> Intersection(this IObservable<ISource> sourceObservable, string filter)
{
return sourceObservable
.SelectMany((source, index) => source.ObserveData(filter).Select(values => new { Index = index, Values = values }))
.Scan(ImmutableDictionary<int, IEnumerable<string>>.Empty, (agg, tuple) => agg.SetItem(tuple.Index, tuple.Values))
.Select(dictionary => dictionary.Values.Aggregate(Enumerable.Empty<string>(), (agg, values) => agg.Any() ? agg.Intersect(values) : values).ToArray());
}
}
public class IntersectionTest
{
internal class Source : ISource
{
private readonly IObservable<IEnumerable<string>> _observable;
public Source(IObservable<IEnumerable<string>> observable)
{
_observable = observable;
}
public IObservable<IEnumerable<string>> ObserveData(string filter)
{
return _observable;
}
}
[Fact]
public void ShouldIntersectValues()
{
TestScheduler scheduler = new TestScheduler();
var sourceA = new Source(scheduler.CreateColdObservable(
new Recorded<Notification<IEnumerable<string>>>(TimeSpan.FromSeconds(1).Ticks, Notification.CreateOnNext<IEnumerable<string>>(new string[] { "a", "b" })),
new Recorded<Notification<IEnumerable<string>>>(TimeSpan.FromSeconds(3).Ticks, Notification.CreateOnNext<IEnumerable<string>>(new string[] { "a", "b", "c" }))
));
var sourceB = new Source(scheduler.CreateColdObservable(
new Recorded<Notification<IEnumerable<string>>>(TimeSpan.FromSeconds(1).Ticks, Notification.CreateOnNext<IEnumerable<string>>(new string[] { "a", "c" })),
new Recorded<Notification<IEnumerable<string>>>(TimeSpan.FromSeconds(3).Ticks, Notification.CreateOnNext<IEnumerable<string>>(new string[] { "b", "c" }))
));
var sources = scheduler.CreateColdObservable(
new Recorded<Notification<ISource>>(TimeSpan.FromSeconds(1).Ticks, Notification.CreateOnNext<ISource>(sourceA)),
new Recorded<Notification<ISource>>(TimeSpan.FromSeconds(2).Ticks, Notification.CreateOnNext<ISource>(sourceB))
);
var observer = scheduler.Start(() => sources.Intersection("test"), 0, 0, TimeSpan.FromSeconds(6).Ticks);
IEnumerable<string>[] actual = observer.Messages
.Select(message => message.Value)
.Where(notification => notification.Kind == NotificationKind.OnNext && notification.HasValue)
.Select(notification => notification.Value)
.ToArray();
IEnumerable<string>[] expected = new []
{
new [] { "a", "b" },
new [] { "a" },
new [] { "a", "c" },
new [] { "b", "c" }
};
Assert.Equal(expected.Length, actual.Length);
foreach (var tuple in expected.Zip(actual, (e, a) => new { Expected = e, Actual = a }))
{
Assert.Equal(tuple.Expected, tuple.Actual);
}
}
}
This approach has the added benefit of not re-querying existing sources when a new source is added but will recompute the intersection each time any source emits a value.

How about this:
public IObservable<IEnumerable<string>> From(this IObservable<ISource> sources, string filter)
{
return sources
.Scan(Observable.Empty<IEnumerable<string>>(), (agg, source) => Observable.Merge(agg, source.ObserveData(filter)))
.Switch();
}
Be aware, that every time a new source is emitted from sources all the sources that have been emitted previously will have their ObserveData method called again. Therefore this solution doesn't scale particularly well but does meet your 'If a new source is added then everything should re-evaluate' requirement

Related

How to combine GroupedObservables in rx.net?

I have one observable that I use GroupBy on to get a number of streams. I actually want a Scan result over each sub-stream. Let's say the observable is over product prices and the scan result is average price per product type.
I have another stream of events pertaining to those 'products' (let's say "show product price" events) and I want to combine it with the previous stream's latest product price. So the Scan output per group needs to be combined with each element of the event stream to get the latest average price for that event's product.
For some reason I cannot get the right syntax and I have been bashing away at this all day. Can someone please help?
Update
I am adding the code below to illustrate the approximate intent.
public class Node
{
private List<int> Details = new List<int>();
public void AddInfo(int x)
{
Details.Add(x );
}
public Node(int x)
{
Details.Add(x);
}
public int Index => Details[0]%10; //just to simplify the grouping and debugging
public int Latest => Details.Last();
}
public class Message
{
private static Random _random = new Random();
public int MessageNodeInfo { get; private set; }
public Message()
{
MessageNodeInfo = _random.Next();
}
}
public class AccumulatingInfoTest
{
private static Random _random=new Random();
private IObservable<Message> MessageStream()
{
TimeSpan timeSpan = TimeSpan.FromSeconds(0.5);
var ret= Observable.Generate(0,
_ => { return true; },
_ => { return 0; },
_ => { return new Message(); },
_=> timeSpan)
.Publish()
.RefCount();
return ret;
}
public class ArbitraryCommonClass
{
public int K { get; set; }
public Message M { get; set; }
public Node D { get; set; }
public ArbitraryCommonClass Combine(ArbitraryCommonClass a)
{
return new ArbitraryCommonClass()
{
K = this.K,
M = this.M ?? a.M,
D = this.D ?? a.D
};
}
}
public void Start()
{
var inputStream = MessageStream();
inputStream.Subscribe(y => Console.WriteLine("Input: K " + y.MessageNodeInfo % 10 + " V " + y.MessageNodeInfo));
var nodeInfoStream = inputStream
.Select(nodeInfo => new Node(nodeInfo.MessageNodeInfo))
.GroupBy(node => node.Index)
.Select(groupedObservable => new
{
Key = groupedObservable.Key,
Observable = groupedObservable
.Scan(
(nodeAcc, node) => { nodeAcc.AddInfo(node.Latest); return nodeAcc; }
)
.Select(a => new ArbitraryCommonClass() { K = a.Index, M = (Message)null, D = a })
}
);
var groupedMessageStream =
inputStream
.GroupBy(
m => new Node(m.MessageNodeInfo).Index
)
.Select(a => new
{
Key =a.Key,
Observable = a.Select(b => new ArbitraryCommonClass() { K = a.Key, M = b, D = null })
});
var combinedStreams = nodeInfoStream
.Merge(groupedMessageStream)
.GroupBy(s => s.Key)
.Select(grp => grp
.Scan(
(state, next) => new { Key = state.Key, Observable = Observable.CombineLatest(state.Observable, next.Observable, (x, y) => { return x.Combine(y); }) }
)
)
.Merge()
.SelectMany(x => x.Observable.Select(a=>a));
combinedStreams.Where(x=>x.M!=null).Subscribe(x => Console.WriteLine(x.K + " " + x.M.MessageNodeInfo + " " + x.D.Latest));
}
}
Assuming the following class:
public class Product
{
public string Type { get; set; } = "Default";
public decimal Price { get; set; }
}
Here's a use of GroupBy with Scan (shows the average product price grouped by type). The trick is to Select over the grouped observable to get to the individual groupings, do whatever, then (presumably) merge them back together. You could collapse the Select and the Merge into a single SelectMany, but it can be easier to read when separated:
var productSubject = new Subject<Product>();
var printSignal = new Subject<Unit>();
var latestAverages = productSubject.GroupBy(p => p.Type)
.Select(g => g
.Scan((0, 0.0m), (state, item) => (state.Item1 + 1, state.Item2 + item.Price)) //hold in state the count and the running total for each group
.Select(t => (g.Key, t.Item2 / t.Item1)) //divide to get the average
)
.Merge()
.Scan(ImmutableDictionary<string, decimal>.Empty, (state, t) => state.SetItem(t.Key, t.Item2)); //Finally, cache the average by group.
printSignal.WithLatestFrom(latestAverages, (_, d) => d)
.Subscribe(avgs =>
{
foreach (var avg in avgs)
{
Console.WriteLine($"ProductType: {avg.Key}. Average: {avg.Value}");
}
Console.WriteLine();
});
var productsList = new List<Product>()
{
new Product { Price = 1.00m },
new Product { Price = 2.00m },
new Product { Price = 3.00m },
new Product { Price = 2.00m, Type = "Alternate" },
new Product { Price = 4.00m, Type = "Alternate" },
new Product { Price = 6.00m, Type = "Alternate" },
};
productsList.ForEach(p => productSubject.OnNext(p));
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 4.0m });
printSignal.OnNext(Unit.Default);
productSubject.OnNext(new Product { Price = 8.0m, Type = "Alternate" });
printSignal.OnNext(Unit.Default);
This uses nuget package System.Collections.Immutable.

AddToSetEach operator won't return the correct query (Builders v 2.4.4)

I've some code to update an array (add to set). The code is currently using the legacy builder:
var data = new[] { 10, 20, 30 };
var fieldName = "data";
var oldWay = Update.AddToSetEach(fieldName , new BsonArray(data));
Console.WriteLine($"Old way:\n {oldWay.ToJson()} \n\n");
This works perfectly and prints:
Old way:
{ "$addToSet" : { "users" : { "$each" : [10, 20, 30] } } }
But when trying to use the new Builders class, I can't get it to work correctly. I'm using MongoDB.Driver 2.4.4. My code:
var data = new[] { 10, 20, 30 };
var fieldName = "data";
var newWay = Builders<BsonDocument>.Update.AddToSetEach(fieldName, new BsonArray(data)).ToJson();
Console.WriteLine($"New way:\n {newWay} \n\n");
The output is:
New way:
{ "_t" : "AddToSetUpdateDefinition`2" }
Any thoughts?
Thank you!
I don't think .ToJson() renders the string how you want it to on a FilterDefinition. It used to be a little easier in the old driver.
Someone wrote an extension method though:
public static class MongoExtensions
{
public static BsonDocument RenderToBsonDocument<T>(this UpdateDefinition<T> filter)
{
var serializerRegistry = BsonSerializer.SerializerRegistry;
var documentSerializer = serializerRegistry.GetSerializer<T>();
return filter.Render(documentSerializer, serializerRegistry);
}
}
Then you can
var data = new[] { 10, 20, 30 };
var fieldName = "data";
var newWay = Builders<BsonDocument>.Update.AddToSetEach(fieldName, new BsonArray(data));
var newWayJson = newWay .RenderToBsonDocument().ToJson();
Console.WriteLine($"New way:\n {newWayJson } \n\n");

Controlling (start/stop) IObservable through ToggleSwitch using ReactiveUI

I am trying to hook a stream (IObservable) to be controlled through ToggleSwitch in a UWP project. The expectation is that I start the streaming when the switch is in On state and stop when it is in Off state.
So the thought is to
1. Create two commands, one to start the stream and another to stop the stream.
2. Create two Observables that monitors the switch state and InvokeCommand when the condition is right.
ViewModel
public class MainPageViewModel : ViewModelBase
{
public ReactiveCommand<Unit, (long, float)> StreamCommand { get; }
public ReactiveCommand<Unit, Unit> StopCommand { get; }
public IObservable<(long, float)> FlowStream { get; set; }
private bool _isStreamOn;
public bool IsStreamOn
{
get => _isStreamOn;
set => this.RaiseAndSetIfChanged(ref _isStreamOn, value);
}
public MainPageViewModel()
{
var stream = GetStream();
var canSwitchOn = this.WhenAnyValue(x => x.IsStreamOn);
var canSwitchOff = this.WhenAnyValue(x => x.IsStreamOn, isOn => isOn != true);
FlowStream = StreamCommand = ReactiveCommand.CreateFromObservable(
() =>
{
stream.Start();
return Observable.FromEventPattern<StreamDataEventArgs<(long, INumeric, INumeric, INumeric)>>(
h => stream.DataAvailable += h,
h => stream.DataAvailable -= h)
.SelectMany(e => e.EventArgs.Data)
.Select(item => item));
}, canSwitchOn);
StopCommand = ReactiveCommand.Create(
() =>
{
stream.Stop();
IsStreamOn = false;
}, canSwitchOff);
canSwitchOff.InvokeCommand(StopCommand);
canSwitchOn.InvokeCommand(StreamCommand);
}
}
View
public sealed partial class MainPage : Page, IViewFor<MainPageViewModel>
{
public MainPage()
{
InitializeComponent();
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
ViewModel = new MainPageViewModel();
this.WhenActivated(subscription =>
{
subscription(this.OneWayBind(this.ViewModel,
vm => vm.StreamCommand,
v => v.chart.SeriesCollection[0].Stream)); // Chart take care of displaying data
subscription(this.Bind(this.ViewModel,
vm => vm.IsStreamOn,
v => v.streamToggle.IsOn));
});
}
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainPageViewModel)value; }
}
public MainPageViewModel ViewModel
{
get { return (MainPageViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(MainPageViewModel), typeof(MainPage), null);
}
However, the InvokeCommand fails, as it requires the ReactiveCommands to take the bool, instead of Unit.
Any idea how I can invoke a command when certain conditions are met?
If you want to turn a stream (IObservable<(long, float)> FlowStream) on and off based on a IObservable<bool> IsStreamOn observable then you can do this:
IObservable<(long, float)> outputStream =
IsStreamOn
.Select(flag => flag ? FlowStream : Observable.Never<(long, float)>())
.Switch();
So each time IsStreamOn produces a true you start getting values from FlowStream, otherwise the values stop.
This assumes that FlowStream is hot. If not, do this:
IObservable<(long, float)> outputStream =
FlowStream
.Publish(fs =>
IsStreamOn
.Select(flag => flag ? fs : Observable.Never<(long, float)>())
.Switch());
Here's a simple test:
void Main()
{
IObservable<long> outputStream =
FlowStream
.Publish(fs =>
IsStreamOn
.Select(flag => flag ? fs : Observable.Never<long>())
.Switch());
using (outputStream.Subscribe(Console.WriteLine))
{
IsStreamOn.OnNext(true);
Thread.Sleep(TimeSpan.FromSeconds(2.5));
IsStreamOn.OnNext(false);
Thread.Sleep(TimeSpan.FromSeconds(3.0));
IsStreamOn.OnNext(true);
Thread.Sleep(TimeSpan.FromSeconds(3.0));
}
}
IObservable<long> FlowStream = Observable.Interval(TimeSpan.FromSeconds(1.0));
Subject<bool> IsStreamOn = new Subject<bool>();
This produces:
0
1
5
6
7
Given the comments re actually calling .Start() and .Stop() then try something like this:
IObservable<(long, float)> outputStream =
Observable
.Create<(long, float)>(o =>
{
var stream = GetStream();
return
FlowStream
.Publish(fs =>
IsStreamOn
.Do(flag => { if (flag) stream.Start(); else stream.Stop(); })
.Select(flag => flag ? fs : Observable.Never<(long, float)>())
.Switch())
.Subscribe(o);
});
In these scenarios with your observables I tend to do
var canSwitchOn = this.WhenAnyValue(x => x.IsStreamOn).Select(_ => Unit.Default);
That will allow you not to have the bool passed along to the command.
oh also you may want a where() clause in this cause if you want to trigger a command in the right condition.
eg.
var switchOn = this.WhenAnyValue(x => x.IsStreamOn).Where(x => x).Select(_ => Unit.Default);
var switchOff = this.WhenAnyValue(x => x.IsStreamOn).Where(x => !x).Select(_ => Unit.Default);

System.Linq.Dynamic IEnumerable OrderBy

var list = new List<stu> {
new stu{ id=1,name="123" },
new stu{ id=2,name="a123" },
new stu{ id=3,name="b123" },
new stu{ id=4,name="c123" },
new stu{ id=5,name="d123" },
new stu{ id=6,name="e123" },
new stu{ id=7,name="f123" },
};
var data = list.Select("name").OrderBy("name");
The Select Method return IEnumerable type,then OrderBy,however throw error
No property or field 'name' exists in type 'String'
Your code is a bit wonky, or at least incomplete, so I wasn't able to quite figure out what you're up to outside of this snippet. Assuming you have something like this:
class stu
{
private int m_id;
private string m_name;
public int Id { get { return m_id; } set { m_id=value; } }
public string Name { get { return m_name; } set { m_name=value; } }
public stu(int id,string name)
{
m_id=id;
m_name=name;
}
}
It appears to me that your code wouldn't even compile on numerous fronts. This could be because I am using an older Visual Studio. To get this to compile, I changed the first part like so (you'll note that I mixed up the alpha order to allow you to see that the sorting actually happens):
var list = new List<stu>
{
new stu(1,"123"),
new stu(2,"f123"),
new stu(3,"b123"),
new stu(4,"e123"),
new stu(5,"d123"),
new stu(6,"c123"),
new stu(7,"a123"),
};
After that, it looks from your code as if you are expecting back a list of stu, but are getting back a list of string. What I would do here is order the list first, then select and get back a sorted list of strings.
var data = list.OrderBy(s => s.Name).Select(s => s.Name).ToList();
foreach(string str in data) { Console.WriteLine(str); }
If what you really want back on the select is a sorted List of stu then you would modify like so.
var data = list.OrderBy(s => s.Name);
foreach(stu s in data) { Console.WriteLine(s.Name); }
Let me know if I am missing your intent.

UnitTesting EF6 with OfType<T>

I am trying to follow the guidelines provided http://msdn.microsoft.com/en-us/library/dn314429.aspx by Microsoft for Unittesting DbSets. All was going well - as they documented. Until I got to some code which works with a inheritance table. Since OfType() is an extension method, I cannot figure out how to create a Mock which will work to keep my code testable.
To clarify: I am trying to Test My Service Layer, which take a DBContext which is Injected, and which exposes several DbSets. In particular, I have an abstract History class, which has concrete derived types of StaffHistory, ContactHistory, etc. As a result, I only have 1 DbSet on my Dbcontext, which is of type History. I then use the Extension method OfType to set the discriminator and query the particular type.
When I create a Mock DbSet all usually works fine, except the OfType extension method fails, reporting NullReference Exception.
Any ideas or tips?
Service Layer:
public IEnumerable<ContactHistory> GetContactHistory(int ContactId, int AgeInDays)
{
var age = DateTimeOffset.Now.AddDays(-Math.Abs(AgeInDays));
return context.History.OfType<ContactHistory>()
.Where(h => h.ContactId == ContactId && h.CreatedAt >= age)
.AsEnumerable();
}
Unit Test Code:
[TestMethod]
public void History_Returns_Limited_Results()
{
var testData = new List<ContactHistory> {
new ContactHistory {
ContactId = 1,
CreatedAt = DateTimeOffset.Now,
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now.AddDays(-61),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now.AddDays(-60),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = DateTimeOffset.Now,
UserName = "UserA",
Action = "Action",
}
}.AsQueryable();
// Setup
var mockContext = new Mock<IPEContext>();
var mockSet = new Mock<IDbSet<History>>();
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.Provider).Returns(testData.Provider);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.Expression).Returns(testData.Expression);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable<ContactHistory>>().Setup(m => m.GetEnumerator()).Returns(testData.GetEnumerator());
mockContext.Setup(c => c.History).Returns(mockSet.Object);
// Test
var service = new HistoryService(mockContext.Object);
var historyFound = service.GetContactHistory(4, 60);
// Verify
Assert.IsNotNull(historyFound);
Assert.AreEqual(2, historyFound.Count());
}
Is there something flawed in my approach? Is there something flawed in how I have setup my mock? This was following the Microsoft Article I mentioned above so that I could test service logic acting on a DbSet. The only flaw seems to be the Extension Method - not sure how I should work around that.
OK - I have figured this out. Of course there was a simple answer, but one which eluded me, because I had already mapped the Linq Provider and all in as the Type IQueryable. If you are using the .OfType() method, your mock must return on the Untyped Queryable method.
Here is the test code to allow the Method to work properly:
[TestMethod]
public void History_Returns_Limited_Results()
{
var today = new DateTimeOffset(DateTime.Today, DateTimeOffset.Now.Offset);
var testData = new List<ContactHistory> {
new ContactHistory {
ContactId = 1,
CreatedAt = today,
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today.AddDays(-61),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today.AddDays(-60),
UserName = "UserA",
Action = "Action",
},
new ContactHistory {
ContactId = 4,
CreatedAt = today,
UserName = "UserA",
Action = "Action",
}
}.AsQueryable();
// Setup
var mockContext = new Mock<IPEContext>();
var mockSet = new Mock<IDbSet<History>>();
mockSet.As<IQueryable>().Setup(m => m.Provider).Returns(testData.Provider);
mockSet.As<IQueryable>().Setup(m => m.Expression).Returns(testData.Expression);
mockSet.As<IQueryable>().Setup(m => m.ElementType).Returns(testData.ElementType);
mockSet.As<IQueryable>().Setup(m => m.GetEnumerator()).Returns(testData.GetEnumerator());
mockContext.Setup(c => c.History).Returns(mockSet.Object);
// Test
var service = new HistoryService(mockContext.Object);
var contact = new Person { ContactId = 4 };
var historyFound = service.GetContactHistory(contact, 60);
// Verify
Assert.IsNotNull(historyFound);
Assert.AreEqual(2, historyFound.Count());
}