How to combine GroupedObservables in rx.net? - reactive-programming

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.

Related

Filters not working in my repository in ASP.NET Core

I have these parameters in a class:
public class UserParams
{
public string Gender {get; set;}
public int MinAge {get; set;} = 1;
public int MaxAge {get; set;} = 19;
}
The query is done in the repository as shown below. First is to query for the child sex or gender and the second is to query for the child sex or gender
var query = _context.Children.AsQueryable();
query = query.Where(c => c.Sex == userParams.Gender);
var minchildDob = DateTime.Today.AddYears(-userParams.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-userParams.MinAge);
query = query.Where(u => u.DateOfBirth >= minchildDob && u.DateOfBirth <= maxchildDob);
return await PagedList<Child>.CreateAsync(query.AsNoTracking(), userParams.PageNumber, userParams.PageSize);
The gender filter returns empty array of children and the minchildDob and maxchildDob too not working
if (!string.IsNullOrEmpty(temp.Gender))
{
all = all.Where(u => new[] { "men", "women" }.Contains(u.sex));
//all = all.Where(t => t.sex == temp.Gender);
}
=======================Update=======================
var temp = new UserParams();
temp.Gender = "men";
var minchildDob = DateTime.Today.AddYears(-temp.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-temp.MinAge);
IEnumerable<Table> all = from m in _context.data
select m;
_logger.LogError("all data");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (!string.IsNullOrEmpty(temp.Gender)) {
all = all.Where(t => t.sex == temp.Gender);
}
_logger.LogError("filter gender");
foreach (var item in all) {
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (temp.MaxAge > 0) {
all = all.Where(t => t.birthday >= minchildDob && t.birthday <= maxchildDob);
}
_logger.LogError("filter age");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");

Dynamic list using array from anthor list

My application is ASP.NET MVC 5 / SQL Server.
I am trying to select specific columns from a list based on an array:
First list has 200 columns: Age, Gender, .....
var list1 = _reportRepository.ShowMasteView().ToList();
Second list has 20 columns: Age, Gender, ......
From the view I select the items to be displayed:
string[] lits2 = showColumn.Where(c => c.Value == true).Select(c=> c.Key).ToArray();
I get
To get these two specific columns, I tried
var nList = list1.Select(t2 => lits2.Any(t1 => t2.Contains(t1)));
I get an error
Can not resolve symbol "Contains"
I was able to do it using the following
var keys = "Age,Gender";
var connection =
ConfigurationManager.ConnectionStrings["DALEntities"].ConnectionString;
using (var dataAdapter = new SqlDataAdapter("SELECT " + keys
+ " from dbo.vw_MasterView", connection))
{
var dataTable = new DataTable();
dataAdapter.Fill(dataTable);
dataAdapter.FillSchema(dataTable, SchemaType.Mapped);
return dataTable;
}
Is there a better way in linq?
From my understand it appears you are trying to extract/select a dynamic object that only has the desired properties/columns.
This can be achieved by building a dynamic expression/function to apply to the Select
The following builds an expression based on the model type and the provided properties
static class DynamicExtensions {
public static IQueryable<dynamic> SelectDynamic<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
var selector = query.BuildSelectorFor(propertyNames);
return query.Select(selector);
}
static Expression<Func<TModel, dynamic>> BuildSelectorFor<TModel>(this IQueryable<TModel> query, ISet<string> propertyNames) {
var modelType = typeof(TModel);
var properties = modelType.GetProperties().Where(p => propertyNames.Contains(p.Name));
// Manually build the expression tree for
// the lambda expression v => new { PropertyName = v.PropertyName, ... }
// (TModel v) =>
var parameter = Expression.Parameter(modelType, "v");
// v.PropertyName
var members = properties.Select(p => Expression.PropertyOrField(parameter, p.Name));
var addMethod = typeof(IDictionary<string, object>).GetMethod(
"Add", new Type[] { typeof(string), typeof(object) });
// { { "PropertyName", v.PropertyName}, ... }
var elementInits = members.Select(m =>
Expression.ElementInit(addMethod, Expression.Constant(m.Member.Name), Expression.Convert(m, typeof(object))));
// new ExpandoObject()
var newExpando = Expression.New(typeof(ExpandoObject));
// new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
var expando = Expression.ListInit(newExpando, elementInits);
// (TModel v) => new ExpandoObject() { { "PropertyName", v.PropertyName}, ... }
var lambdaExpression = Expression.Lambda<Func<TModel, dynamic>>(expando, parameter);
return lambdaExpression;
}
}
This takes advantage of ExpandoObject whose members can be dynamically added and removed at run time.
The following test was used as an example of how the above function is invoked.
[TestMethod]
public void DynamicList() {
var list1 = new List<Person>
{
new Person{ Gender = "Male", Age = 10, FirstName = "Nama1", SampleNumber = 12},
new Person{ Gender = "Male", Age = 12, FirstName = "Nama2", SampleNumber = 13},
new Person{ Gender = "Female", Age = 13, FirstName = "Nama3", SampleNumber = 14},
new Person{ Gender = "Male", Age = 14, FirstName = "Nama4", SampleNumber = 15},
};
var keys = new string[] { "Age", "Gender", };
var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));
foreach (IDictionary<string, object> row in nList) {
var msg = $"{{ {keys[0]} = {row[keys[0]]}, {keys[1]} = {row[keys[1]]} }}";
Debug.WriteLine(msg);
}
}
and produces the following output
{ Age = 10, Gender = Male }
{ Age = 12, Gender = Male }
{ Age = 13, Gender = Female }
{ Age = 14, Gender = Male }
The dynamic objects can be used in the View and it is a simple matter of calling the desired members.
For example suppose you have a model as follows
public class MyViewModel {
public string MyProperty { get; set; }
public string[] Keys { get; set; }
public List<dynamic> MyDynamicProperty { get; set; }
}
that was populated with data and given to the view
var list1 = _reportRepository.ShowMasteView();
var keys = new string[] { "Age", "Gender", };
var nList = list1.AsQueryable().SelectDynamic(new HashSet<string>(keys));
var viewModel = new MyViewModel {
MyProperty = "Hello World",
MyDynamicProperty = nList.ToList(),
Keys = keys
};
return View(viewModel);
Then in the view you can use the model as desired, casting to get access to members in the expando object.
#model MyViewModel
...
<h2>#Model.MyProperty</h2>
<table>
<tr>
#foreach(string key in Model.Keys) {
<th>#key</th>
}
</tr>
#foreach (IDictionary<string, object> row in Model.MyDynamicProperty) {
<tr>
#foreach(string key in Model.Keys) {
<td>#row[#key]</td>
}
</tr>
}
</table>
I think you just need to use Contains on your list2.
var nList = list1.Where(t => lits2.Contains(t1));
Contains is a method for Lists. The code you had was trying to use it on a string.
If you have two list of a person's class
public class Person
{
public int id { get; set; }
public string name { get; set; }
}
If the lists are as below:
var list1 = new List<Person>
{
new Person{ id = 1, name = "Nama1"},
new Person{ id = 2, name = "Nama2"},
new Person{ id = 3, name = "Nama3"},
new Person{ id = 4, name = "Nama4"},
};
var list2 = new List<Person>
{
new Person{ id = 1, name = "Nama1"},
new Person{ id = 2, name = "Nama2"},
};
You can filter in the following ways
var keys = list2.Select(x => x.id).ToList();
var filter1= list1.Where(x => keys.Contains(x.id)).ToList();
var filter2= list1.Where(x => keys.Contains(x.id)).Select(x => new { x.name }).ToList();
var filter3= list1.Select(x => new
{
id = x.id,
name = x.name,
check = keys.Contains(x.id)
}).Where(x => x.check).ToList();
If you have array of string
you can use below code
array string same
var lis1 = new string[] {"name1", "name2","name3" };
var lis2 = new string[] { "name1" };
You can filter array of string in the following ways
var items1= lis1.Where(x=>lis2.Contains(x)).ToList();
var items= lis1.Select(x=> new { x, check= lis2.Contains(x) }).Where(x=>x.check == true).ToList();

How to construct a dynamic where filter in EF.Core to handle equals, LIKE, gt, lt, etc

Please how do we construct a dynamic where filter in EF.Core to handle:
Query.Where(fieldName, compareMode, value)
I basically Expect to use it like below:
[HttpGet(Name = nameof(GetStaff))]
public IActionResult GetStaffAsync([FromQuery] QueryParams p)
{
var s = db.Staff.AsNoTracking()
.Where(p.filter_field, p.filter_mode, p.filter_value)
.OrderByMember(p.sortBy, p.descending);
var l = new Pager<Staff>(s, p.page, p.rowsPerPage);
return Ok(l);
}
//Helpers
public class QueryParams
{
public bool descending { get; set; }
public int page { get; set; } = 1;
public int rowsPerPage { get; set; } = 5;
public string sortBy { get; set; }
public onject filter_value { get; set; }
public string filter_field { get; set; }
public string filter_mode { get; set; }
}
public class Pager<T>
{
public int pages { get; set; }
public int total { get; set; }
public IEnumerable<T> Items { get; set; }
public Pager(IEnumerable<T> items, int offset, int limit)
{
Items = items.Skip((offset - 1) * limit).Take(limit).ToList<T>();
total = items.Count();
pages = (int)Math.Ceiling((double)total / limit);
}
}
Assuming all you have is the entity type and strings representing the property, comparison operator and the value, building dynamic predicate can be done with something like this:
public static partial class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.Property);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
private static Expression MakeComparison(Expression left, string comparison, string value)
{
switch (comparison)
{
case "==":
return MakeBinary(ExpressionType.Equal, left, value);
case "!=":
return MakeBinary(ExpressionType.NotEqual, left, value);
case ">":
return MakeBinary(ExpressionType.GreaterThan, left, value);
case ">=":
return MakeBinary(ExpressionType.GreaterThanOrEqual, left, value);
case "<":
return MakeBinary(ExpressionType.LessThan, left, value);
case "<=":
return MakeBinary(ExpressionType.LessThanOrEqual, left, value);
case "Contains":
case "StartsWith":
case "EndsWith":
return Expression.Call(MakeString(left), comparison, Type.EmptyTypes, Expression.Constant(value, typeof(string)));
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
private static Expression MakeString(Expression source)
{
return source.Type == typeof(string) ? source : Expression.Call(source, "ToString", Type.EmptyTypes);
}
private static Expression MakeBinary(ExpressionType type, Expression left, string value)
{
object typedValue = value;
if (left.Type != typeof(string))
{
if (string.IsNullOrEmpty(value))
{
typedValue = null;
if (Nullable.GetUnderlyingType(left.Type) == null)
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else
{
var valueType = Nullable.GetUnderlyingType(left.Type) ?? left.Type;
typedValue = valueType.IsEnum ? Enum.Parse(valueType, value) :
valueType == typeof(Guid) ? Guid.Parse(value) :
Convert.ChangeType(value, valueType);
}
}
var right = Expression.Constant(typedValue, left.Type);
return Expression.MakeBinary(type, left, right);
}
}
Basically building property accessor (with nested property support), parsing the comparison operator and calling the corresponding operator/method, dealing with from/to string and from/to nullable type conversions. It can be extended to handle EF Core specific functions like EF.Functions.Like by adding the corresponding branch.
It can be used directly (in case you need to combine it with other predicates) or via custom extension method like this:
public static partial class QueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, string comparison, string value)
{
return source.Where(ExpressionUtils.BuildPredicate<T>(propertyName, comparison, value));
}
}
based on Ivans answer this is what i came up with
public static class ExpressionUtils
{
public static Expression<Func<T, bool>> BuildPredicate<T>(string propertyName, string comparison, object value)
{
var parameter = Expression.Parameter(typeof(T));
var left = propertyName.Split('.').Aggregate((Expression)parameter, Expression.PropertyOrField);
var body = MakeComparison(left, comparison, value);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
static Expression MakeComparison(Expression left, string comparison, object value)
{
var constant = Expression.Constant(value, left.Type);
switch (comparison)
{
case "==":
return Expression.MakeBinary(ExpressionType.Equal, left, constant);
case "!=":
return Expression.MakeBinary(ExpressionType.NotEqual, left, constant);
case ">":
return Expression.MakeBinary(ExpressionType.GreaterThan, left, constant);
case ">=":
return Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, constant);
case "<":
return Expression.MakeBinary(ExpressionType.LessThan, left, constant);
case "<=":
return Expression.MakeBinary(ExpressionType.LessThanOrEqual, left, constant);
case "Contains":
case "StartsWith":
case "EndsWith":
if (value is string)
{
return Expression.Call(left, comparison, Type.EmptyTypes, constant);
}
throw new NotSupportedException($"Comparison operator '{comparison}' only supported on string.");
default:
throw new NotSupportedException($"Invalid comparison operator '{comparison}'.");
}
}
}
and some tests
public class Tests
{
[Fact]
public void Nested()
{
var list = new List<Target>
{
new Target
{
Member = "a"
},
new Target
{
Member = "bb"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Target>("Member.Length", "==", 2))
.Single();
Assert.Equal("bb", result.Member);
}
[Fact]
public void Field()
{
var list = new List<TargetWithField>
{
new TargetWithField
{
Field = "Target1"
},
new TargetWithField
{
Field = "Target2"
}
};
var result = list.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<TargetWithField>("Field", "==", "Target2"))
.Single();
Assert.Equal("Target2", result.Field);
}
[Theory]
[InlineData("Name", "==", "Person 1", "Person 1")]
[InlineData("Name", "!=", "Person 2", "Person 1")]
[InlineData("Name", "Contains", "son 2", "Person 2")]
[InlineData("Name", "StartsWith", "Person 2", "Person 2")]
[InlineData("Name", "EndsWith", "son 2", "Person 2")]
[InlineData("Age", "==", 13, "Person 2")]
[InlineData("Age", ">", 12, "Person 2")]
[InlineData("Age", "!=", 12, "Person 2")]
[InlineData("Age", ">=", 13, "Person 2")]
[InlineData("Age", "<", 13, "Person 1")]
[InlineData("Age", "<=", 12, "Person 1")]
public void Combos(string name, string expression, object value, string expectedName)
{
var people = new List<Person>
{
new Person
{
Name = "Person 1",
Age = 12
},
new Person
{
Name = "Person 2",
Age = 13
}
};
var result = people.AsQueryable()
.Where(ExpressionUtils.BuildPredicate<Person>(name, expression, value))
.Single();
Assert.Equal(expectedName, result.Name);
}
}
I modified the answer I found here: Linq WHERE EF.Functions.Like - Why direct properties work and reflection does not?
I chucked together a version for those using NpgSQL as their EF Core provider as you will need to use the ILike function instead if you want case-insensitivity, also added a second version which combines a bunch of properties into a single Where() clause:
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string searchTerm)
{
// Check property name
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
// Check the search term
if(string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var property = typeof(T).GetProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"The property {typeof(T)}.{propertyName} was not found.", nameof(propertyName));
}
// Check the property type
if(property.PropertyType != typeof(string))
{
throw new ArgumentException($"The specified property must be of type {typeof(string)}.", nameof(propertyName));
}
// Get expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the property expression and return it
Expression selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, IEnumerable<string> propertyNames, string searchTerm)
{
// Check property name
if (!(propertyNames?.Any() ?? false))
{
throw new ArgumentNullException(nameof(propertyNames));
}
// Check the search term
if (string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var properties = propertyNames.Select(p => typeof(T).GetProperty(p)).AsEnumerable();
if (properties.Any(p => p == null))
{
throw new ArgumentException($"One or more specified properties was not found on type {typeof(T)}: {string.Join(",", properties.Where(p => p == null).Select((p, i) => propertyNames.ElementAt(i)))}.", nameof(propertyNames));
}
// Check the property type
if (properties.Any(p => p.PropertyType != typeof(string)))
{
throw new ArgumentException($"The specified properties must be of type {typeof(string)}: {string.Join(",", properties.Where(p => p.PropertyType != typeof(string)).Select(p => p.Name))}.", nameof(propertyNames));
}
// Get the expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the expression and return it
Expression selectorExpression = null;
foreach (var property in properties)
{
var previousSelectorExpression = selectorExpression;
selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
if(previousSelectorExpression != null)
{
selectorExpression = Expression.Or(previousSelectorExpression, selectorExpression);
}
}
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}

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

Include in relational data with a where condition

I have this entities:
public class A
{
public List<B> B { get;set; }
}
public class B
{
public DateTime Date { get; set; }
public List<C> C { get; set; }
}
public class C { }
and I need to get C from A where B.Date > DateTime.Now..
I'm trying something like this:
var users = _db.A
.Select(a => new
{
A = a,
B = a.B
.Where(b => b.Date >= DateTime.Now).Select(p => new
{
B = b,
C = b.C
})
})
.AsEnumerable()
.Select(a => a.A)
.ToList();
but C is always null.
How can I get C?
UPDATE
If I set virtual in 'List C' it works!!
How can I make this work without setting virtual?
You can try to build the object graph manually. The following will run only a single database query (when the first foreach loop executes, the rest is performed in memory):
var userQuery = _db.A
.Select(a => new
{
A = a,
B = a.B.Where(b => b.Date >= DateTime.Now).Select(p => new
{
B = b,
C = b.C
})
});
var users = new List<A>();
foreach (var u in userQuery)
{
A a = u.A;
a.B = new List<B>();
foreach (var x in u.B)
{
B b = x.B;
b.C = x.C;
a.B.Add(b);
}
users.Add(a);
}