ObservableAsPropertyHelper - have to access Value to get it to subscribe? - reactiveui

I recently updated my ReactiveUI nugget packages to the latest pre-release versions (v5.99.4-beta). It has been a while since I did the update, but many of my tests on ViewModel's failed.
The debugger seemed to indicate that my normal sequences of Rx weren't getting subscribed to. For example:
_executeSearch = ReactiveCommand.Create();
var paper = _executeSearch
.Where(x => x is string)
.SelectMany(x => _searchParser.GetPaperFinders(x as string))
.SelectMany(x => x.FindPaper());
paper
.Select(x => x.Item1.Title)
.ToProperty(this, x => x.Title, out _TitleOAPH, "");
And my tests code looked something like this (cut for brevity):
INavService obj = Mock...
ISearchStringParser parser = ...
var vm = new AddCDSPaperViewModel(obj, parser);
vm.CDSLookupString = "1234"; // trigger the "search"
... (test scheduler is advanced to wait for search results to show up
Assert.AreEqual("title", vm.Title, "searched for title"); // vm.Title is unset, though it should be!
I looked at the reactiveui source code on github and discovered the following for the "value" property on the ObservableAsPropertyHelper object:
public T Value {
get {
_inner = _inner ?? _source.Connect();
return _lastValue;
}
}
The key line there is "_source.Connect()" - in short, the sequence isn't subscribed to until someone access Value. In my tests, if I put a "vm.Title" before I run any rx sequences, then everything works just fine.
This is surprising behavior (at least to me), in the sense that the ObservableAsPropertyHelper.Value property has to be accessed before it will capture any values. Is this expected behavior? Was it done for efficiency (e.g. lazy instantiation) reasons? If so, what is the proper way around this in tests?
Or am I very mistaken in how this works? :-)

Is this expected behavior?
Yep, this is new for ReactiveUI 6.0.
Was it done for efficiency (e.g. lazy instantiation) reasons?
Correct - it actually can end up saving a lot of memory and work! (15-20% memory as I recall in GitHub for Windows on my early tests)
This is an interesting scenario though - I suspect that in the unit test runner, we should be subscribing on construction, similar to what RxUI 5.x did.
Edit: I would write it this way:
_executeSearch = ReactiveCommand.Create(_ => Observable.Return(x)
.Where(x => x is string)
.SelectMany(x => _searchParser.GetPaperFinders(x as string))
.SelectMany(x => x.FindPaper()));
_executeSearch
.Select(x => x.Item1.Title)
.ToProperty(this, x => x.Title, out _TitleOAPH, "");
Now, you can write your test way easier, without TestScheduler:
var vm = new AddCDSPaperViewModel(obj, parser);
var result = await vm.ExecuteSearch.ExecuteAsync("Some String");
Assert.AreEqual("title", result.Title);

Related

Execution Timeout Expired in nested query with Group By in .NET Core 6

I have this query to fetch the data from Database, but the problem is taking too much time until the it throws below exception:
Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
I tried to add below line in Program.cs
opt.CommandTimeout((int)TimeSpan.FromMinutes(5).TotalSeconds);
but that will not solve the issue and even thought it throws the same exception again.
the query is:
result = await _context.SomeObjects
.AsNoTracking()
.GroupBy(x => new { x.Location, x.Date })
.Select(x => new MyObject
{
Date = x.Key.Date,
Location = x.Key.Location,
MaxValue = x.Max(x => x.Value),
MinValue = x.Min(x => x.Value),
Mean = x.Select(x => (double)x.Value).Mean() ,
Value = x.Sum(x=>(long)x.Value),
Median = x.Select(x=> (double)x.Value).Median(),
Open = x.OrderByDescending(x=>x.Date).First().Value
}).ToListAsync();
Is there anyway to enhance the query, I tried many approach but in every try it throws different exception.
Update:
After start using
MathNet.Numerics.Statistics;
it start throwing
Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary information required to
uniquely identify it and correctly generate results on the client side. This can happen when trying to correlate on keyless entity type.
This can also happen for some cases of projection before 'Distinct' or some shapes of grouping key in case of 'GroupBy'. These should either contain all key
properties of the entity that the operation is applied on, or only contain simple property access expressions.
but this is not the main issue the main issue is with below line
Open = x.OrderByDescending(x=>x.Date).First().Value
If I commented it out the query will working fine.
I will answer my question, I got the hint of solution from #GertArnold,
and I think there is an optimal solution which mentioned by #SvyatoslavDanyliv by comments, he suggested to do that by Stored Procedure and I think this is the optimal solution for huge amount of data, but the solution that let the code work without any exception is fetching data to backend side then do my calculations (doing the calculations by .NET).
Here is the code:
Note: I'm using MathNet.Numerics.Statistics NuGet package to calculate the Median
var allData = _context.SomeObjects.AsNoTracking().AsParallel().ToList();
var result= allData.GroupBy(x => new { x.Location, x.Date }).Select(x =>
{
var Values = x.OrderByDescending(x => x.Date).Select(x => x.Value).AsEnumerable();
return new MyObject
{
Date = x.Key.Date,
Location = x.Key.Location,
MaxValue = x.Max(x => x.Value),
MinValue = x.Min(x => x.Value),
Mean = x.Select(x => (double)x.Value).Mean(),
Value = x.Sum(x => (long)x.Value),
Median = x.Select(x => (double)x.Value).Median(),
Open = Values.First(),
Close = Values.Last(),
};
}
).AsEnumerable();

GroupBy OrderBy Linq - invalid column

I am trying to build a very simple LinQ query used to return a json to my client.
the following works:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.Select(x => x.Count())
However, I would like to retun the count list ordered by type so I wrote the following but it didn't work:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.OrderBy(o => o.Key.type)
.Select(x => x.Count())
It returns a 'invalid column name "type"'
I don't get it, the orderby is occuring after the groupBy, I am using the key properly.
It looks like, the orderBy needs the criteria in the Slect statement to work properly.
For example, the following works:
_context.MyTable
.GroupBy(g => new { type= g.TypeId })
.OrderBy(o => o.Key.type)
.Select(x => {x.Count(), x.Key.type})
But it does not return what i want, I just want to get the count values ordered by type.
Any directions why I get this error.
I am using EF Core if it can help.
S.
Although EF Core 2.1 introduced improvements to LINQ GroupBy translation, the implementation is still far from perfect and produces exceptions in many scenarios due to invalid SQL generation. Your scenario is just one of them and is not working even with the recent at this time EF Core 2.1.1 bits.
The first thing you should do is to submit it to the EF Core issue tracker, so they know and fix it when possible.
The problem seems to be related to the key property aliasing (it also doesn't work if you use the "normal" .GroupBy(e => e.TypeId).OrderBy(g => g.Key)). The current workaround I've found is to use anonymous type key having the same property name(s) as the source(s):
_context.MyTable
.GroupBy(e => new { e.TypeId })
.OrderBy(g => g.Key.TypeId)
.Select(g => g.Count())

Setting visiblilty of control using LINQ

I'm working on refactoring some code to practice LINQ. For some reason I can't get this code to cooperate.
//ActionControls is a ControlCollection
var actionControls = flowLayoutPanel1.FilterControls(c => c is Button);
//TODO: Optimize
foreach(var control in actionControls)
{
control.Visible = workingItemDataTable.AsEnumerable().Any(row => "btn" + row.Field<string>("Name") == control.Name);
}
What I am trying to do now.
flowLayoutPanel1.FilterControls(c => c is Button && c.Name == "btnTaskInfo"//btnTaskInfo is always visible
|| workingItemDataTable.AsEnumerable().Any(row => "btn" + row.Field<string>("Name") == c.Name)).Cast<Button>()
But after casting this as a button, I can not figure out how to set visible = false. Any Advice?
You'd still need to iterate the controls, but you can probably do this, assuming FilterControls isn't much more than an alias for Where:
var actionControls = flowLayoutPanel1.OfType<Button>();
There are some "tricks" and shortcuts for iterating and doing sets within a lambda expression, but it makes the code just look messy and they are mostly just hacks. You can create your own extension ForEach (if there isn't one available to you if you really want to do it anyway).
flowLayoutPanel1.OfType<Button>().ForEach(btn=>{btn.Visible= ... });

What do they mean when they say LINQ is composable?

What does it mean and why (if at all) is it important?
It means you can add additional "operators" to a query. It's important because you can do it extremely efficiently.
For example, let's say you have a method that returns a list (enumerable) of employees:
var employees = GetEmployees();
and another method that uses that one to return all managers:
IEnumerable<Employee> GetManagers()
{
return GetEmployees().Where(e => e.IsManager);
}
You can call that function to get managers that are approaching retirement and send them an email like this:
foreach (var manager in GetManagers().Where(m => m.Age >= 65) )
{
SendPreRetirementMessage(manager);
}
Pop quiz: How many times will that iterate over your employees list? The answer is exactly once; the entire operation is still just O(n)!
Also, I don't need to have separate methods for this. I could compose a query with these steps all in one place:
var retiringManagers = GetEmployees();
retiringManagers = retiringManagers.Where(e => e.IsManager);
retiringManagers = retiringManagers.Where(m => m.Age >= 65);
foreach (var manager in retiringMangers)
{
SendPreRetirementMessage();
}
One cool thing about this is that I can change is at run time, such that I can include or not include one part of the composition inside an if block, such that the decision to use a specific filter is made at run time, and everything still comes out nice and pretty.
I think it means that you can daisy chain your queries, like this
var peterJacksonsTotalBoxOffice
= movies.Where(movie => movie.Director == "Peter Jackson")
.Sum(movie => movie.BoxOffice);

Reproduce a "DELETE NOT IN" SQL Statement via LINQ/Subsonic

I want to do something like DELETE FROM TABLE WHERE ID NOT IN (1,2,3) AND PAGEID = 9
I have a List of IDS but that could be changed if needs be. I can't work out how to get a boolean result for the LINQ parser.
Here is what Subsonic expects I think.
db.Delete(content => content.PageID == ID).Execute();
I can't work out how to do the NOT IN statement. I've tried the List.Contains method but something not quite right.
UPDATE: One alternative is to do:
var items = TABLE.Find(x => x.PageID == ID)'
foreach(var item in items)
{
item.Delete();
}
This hits the database a lot more though
When you say "something not quite right" what exactly do you mean?
I'd expect to write:
List<int> excluded = new List<int> { 1, 2, 3 };
db.Delete(content => !excluded.Contains(content.PageID)).Execute();
Note that you need to call Contains on the array of excluded values, not on your candidate. In other words, instead of saying "item not in collection" you're saying "collection doesn't contain item."
Try .Contains:
db.Delete(content => content.PageID.Contains(<Array containing ID's>).Execute();
(the above is just an example, might need some polishing for your specific situation)
I have found that this works but its not via LINQ
var table = new WebPageContentTable(_db.DataProvider);
var g = new SubSonic.Query.Delete<WebPageContent(_db.DataProvider)
.From(table)
.Where(table.ID)
.NotIn(usedID)
.Execute();
I have found that this does work and via LINQ - however it hits the database multiple times.
var f = WebPageContent.Find(x => !usedID.Any(e => e == x.ID));
if (f.Count > 0)
{
var repo = WebPageContent.GetRepo();
repo.Delete(f);
}
This I imagine would work in one hit to the database but I get an exception thrown in QueryVisitor::VisitUnary
WebPageContent.Delete(x => !usedID.Any(e => e == x.ID));

Resources