Controlling (start/stop) IObservable through ToggleSwitch using ReactiveUI - mvvm

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);

Related

EF Core Value Converter does not run when DB value is null

We have a custom value converter for being able to store a JsonBlob. This works if value in the DB is a string / json string but if the value is null the Deserialize function is never hit. Is there a way to change this? This causes an issue because we would prefer to init an empty collection if there is no value.
The Converter
public class JsonStringConvertor<T> : ValueConverter<T, string> where T : class, new()
{
public JsonStringConvertor(ConverterMappingHints mappingHints = null)
: base(convertToProviderExpression, convertFromProviderExpression, mappingHints) { }
//Work around to dynamically create a ValueComparer<T>. Called using reflection
public ValueComparer<T> CreateValueComparer()
{
var comparer = new ValueComparer<T>
(
(l, r) => Serialize(l) == Serialize(r),
v => v == null ? 0 : Serialize(v).GetHashCode(),
v => Deserialize<T>(Serialize(v))
);
return comparer;
}
private static readonly Expression<Func<T, string>> convertToProviderExpression = v => Serialize(v);
private static readonly Expression<Func<string, T>> convertFromProviderExpression = v => Deserialize<T>(v) ?? new T();
private static string Serialize<TObj>(TObj t) => JsonConvert.SerializeObject(t, EntityFrameworkJson.SerializerSettings);
private static TVal Deserialize<TVal>(string s) => JsonConvert.DeserializeObject<TVal>(s, EntityFrameworkJson.SerializerSettings);
}
Usage
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Model
.GetEntityTypes()
.SelectMany(t => t.ClrType.GetProperties()
.Select(p => new
{
DeclaringEntityType = t.ClrType,
Property = p
}))
.Where(t => GetAttribute<JsonDataAttribute>(t.Property) != null)
.ForEach(t =>
{
//had to use reflection api to get all JsonData attributes, so now we reconstruct the EF PropertyBuilder
var propBuilder = modelBuilder.Entity(t.DeclaringEntityType).Property(t.Property.Name);
//Use old EF6 Naming Convention _Json
var identifier = StoreObjectIdentifier.Create(propBuilder.Metadata.DeclaringEntityType, StoreObjectType.Table);
if (identifier != null)
{
var propName = propBuilder.Metadata.GetColumnName(identifier.Value);
propBuilder.Metadata.SetColumnName(propName + "_Json");
}
//Setup Converter and Comparer
var converterType = typeof(JsonStringConvertor<>).MakeGenericType(t.Property.PropertyType);
var converter = (ValueConverter) Activator.CreateInstance(converterType, (object) null);
var comparerFunc = converter?.GetType().GetMethod("CreateValueComparer");
if (comparerFunc is {})
{
var comparer = (ValueComparer) comparerFunc.Invoke(converter, null);
propBuilder.Metadata.SetValueComparer(comparer);
}
propBuilder.HasConversion(converter);
propBuilder.Metadata.SetValueConverter(converter);
});
Thought it might be the comparer but it still doesn't work when not using the comparer. What i found during testing is that if the value in the database is null then the converter does not run. If i update it to an empty string it works.
we're using Entity Framework Core 5.0.1

Moq MongoDB UpdateOneAsync method for unit testing

I want to moq update method that is using mongodbContext. here is what i am trying to do but its not working. how to pass UpdateResult return type .ReturnsAsync<UpdateResult>(). I am very new to Mongodb database Unit Testing with .net core.
public void UpdateEventAsync_Test()
{
//Arrange
var eventRepository = EventRepository();
var pEvent = new PlanEvent
{
ID = "testEvent",
WorkOrderID = "WorkOrderID",
IsDeleted = false,
IsActive = true,
EquipmentID = "EquipmentID"
};
////Act
mockEventContext.Setup(s => s.PlanEvent.UpdateOneAsync(It.IsAny<FilterDefinition<PlanEvent>>(), It.IsAny<UpdateDefinition<Model.EventDataModel.PlanEvent>>(), It.IsAny<UpdateOptions>(), It.IsAny<System.Threading.CancellationToken>())).ReturnsAsync<UpdateResult>();
var result = eventRepository.UpdateEventAsync(pEvent);
////Assert
result.Should().NotBeNull();
Assert.AreEqual(true, result);
}
below is the code for which i want to write Moq Test
public async Task<bool> UpdateEventAsync(Model.EventDataModel.PlanEvent eventobj)
{
var filter = Builders<Model.EventDataModel.PlanEvent>.Filter.Where(f => f.ID == eventobj.ID);
// TO Do: Use replace instead of update.
var updatestatement = Builders<Model.EventDataModel.PlanEvent>.Update.Set(s => s.IsDeleted, eventobj.IsDeleted)
.Set(s => s.EquipmentID, eventobj.EquipmentID)
.Set(s => s.PlannedStartDateTime, eventobj.PlannedStartDateTime)
.Set(s => s.PlannedEndDatetime, eventobj.PlannedEndDatetime)
.Set(s => s.WorkOrderID, eventobj.WorkOrderID)
.Set(s => s.ResourceID, eventobj.ResourceID)
.Set(s => s.LastUpdatedBy, eventobj.LastUpdatedBy)
.Set(s => s.EventComment, eventobj.EventComment)
.Set(s => s.SiteID, eventobj.SiteID)
.Set(s => s.LastUpdatedDateTime, DateTime.UtcNow.ToString());
UpdateResult updateResult = await _eventContext.PlanEvent.UpdateOneAsync(filter, updatestatement);
return updateResult != null && updateResult.IsAcknowledged && updateResult.ModifiedCount > 0;
}
Either create an instance or mock UpdateResult and return that from the setup
public async Task UpdateEventAsync_Test() {
//...omitted for brevity
var mockUpdateResult = new Mock<UpdateResult>();
//Set up the mocks behavior
mockUpdateResult.Setup(_ => _.IsAcknowledged).Returns(true);
mockUpdateResult.Setup(_ => _.ModifiedCount).Returns(1);
mockEventContext
.Setup(_ => _.PlanEvent.UpdateOneAsync(It.IsAny<FilterDefinition<PlanEvent>>(), It.IsAny<UpdateDefinition<Model.EventDataModel.PlanEvent>>(), It.IsAny<UpdateOptions>(), It.IsAny<System.Threading.CancellationToken>()))
.ReturnsAsync(mockUpdateResult.Object);
//Act
var result = await eventRepository.UpdateEventAsync(pEvent);
//Assert
result.Should().Be(true);
}
Also note that the test needs to be made async to be exercised accurately.

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.

How do you create Expression<Func<T, T>> given the property names of T to map?

Here's what the required function would look like:
public static Expression<Func<T, T>> GetExpression<T>(string propertyNames) where T : class
{
var properties = propertyNames.Split(
new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries
).ToList();
//need help here
}
Currently I'm doing it like this:
_context.Questions.Select(q =>
new Question() {
QuestionId = q.QuestionId,
QuestionEnglish = q.QuestionEnglish
}
).ToList();
And I want to replace it with:
_context.Questions.Select(GetExpression<Question>("QuestionId, QuestionInEnglish")).ToList();
Any help would be greatly appreciated.
You can do it like this;
public static Func<T, T> GetExpression<T>(string propertyNames)
{
var xParameter = Expression.Parameter(typeof(T), "parameter");
var xNew = Expression.New(typeof(T));
var selectFields = propertyNames.Split(',').Select(parameter => parameter.Trim())
.Select(parameter => {
var prop = typeof(T).GetProperty(parameter);
if (prop == null) // The field doesn't exist
{
return null;
}
var xOriginal = Expression.Property(xParameter, prop);
return Expression.Bind(prop, xOriginal);
}
).Where(x => x != null);
var lambda = Expression.Lambda<Func<T, T>>(Expression.MemberInit(xNew, selectFields), xParameter);
return lambda.Compile();
}
Usage;
var list = new List<Question>{new Question{QuestionEnglish = "QuestionName",QuestionId = 1}};
var result = list.Select(GetExpression<Question>("QuestionId, QuestionEnglish"));

The current thread has no Dispatcher associated with it

I'm creating an observable and I'm creating the subscription separately:
class CustomQuery {
string Name;
IObservable<int> Occurrences;
}
public IEnumerable<CustomQuery> GatherCustomQueryObservables()
{
yield return new CustomQuery() {
Name = "NameXXX",
Occurrences = Observable.Create<int>(
observer =>
{
int occurrences = this.webservice.GetOccurrences()
observer.OnNext(occurrences);
return System.Reactive.Disposables.Disposable.Empty;
}
);
}
By other hand, there's another method deals with these CustomQueries:
public void CommitCustomQueryObservables(IEnumerable<CustomQuery> queries)
{
foreach (CustomQuery query in queries)
{
query.Occurrences
.Select(o => o)
.SubscribeOn(System.Reactive.Concurrency.TaskPoolScheduler.Default)
.ObserveOn(System.Reactive.Concurrency.DispatcherScheduler.Current)
.Subscribe(
occurrences =>
{
string strOccurrences = occurrences > 0 ? occurrences.ToString() : "";
this.Label.Text = strOccurrences;
}
);
}
}
Nevertheless, I'm getting a System.InvalidOperationException exception:
The current thread has no Dispatcher associated with it.
The last line of the stacktrace is at
System.Reactive.Concurrency.DispatcherScheduler.get_Current(‌​).
I don't quite figure out how to handle it.
Any ideas?
For Windows Forms you need to use the ControlScheduler for synchronization, not the DispatcherScheduler.
Now you've added the System.Reactive.Windows.Forms package this can be achieved by simply using [observable].ObserveOn([control]); in your example this could be:
public void CommitCustomQueryObservables(IEnumerable<CustomQuery> queries)
{
foreach (CustomQuery query in queries)
{
query.Occurrences
.Select(o => o)
.SubscribeOn(System.Reactive.Concurrency.TaskPoolScheduler.Default)
.ObserveOn(this.Label)
.Subscribe(
occurrences =>
{
string strOccurrences = occurrences > 0 ? occurrences.ToString() : "";
this.Label.Text = strOccurrences;
}
);
}
}