Adding items as range doesn't always trigger ItemsAdded for a ReactiveList - reactiveui

I have two ReactiveList called ListA and ListB. Any item added to ListB is also added to ListA:
this
.WhenAnyValue(x => x.ListB)
.Where(x => x != null)
.Subscribe(list =>
{
ListA.AddRange(list);
list
.ItemsAdded
.Subscribe(item => ListA.Add(item), exceptionHandler)
.DisposeWith(Disposables);
})
.DisposeWith(Disposables);
The issue is that when some UI action triggers ListB.AddRange(items), ListA only gets properly updated when the number of items is small. When it's a few dozens, the onNext action of ItemsAdded is not called (and so is not onError) and I cannot understand why.

ReactiveList<T> has been deprecated and we recommend the use of DynamicData https://github.com/rolandpheasant/DynamicData
The reason is due to a optimisation for UI where we will issue a reset instead when there is a large number of items.

Related

Ignore events on stream until condition is satisfied?

I created a stream from button click events. The button corresponds to a create action on the database. Obviously, I want the database action to fire only once (at least until it completes). Is there a way to ignore events on createButtonStream until Api.create returns? i.e., the first event should call #Api.create, subsequent events should be ignored until #Api.create returns.
createButtonStream
.flatMap(() => Api.create()) //Needs to fire once until doSomething() is called
.onValue(result => doSomething(result))
The only way that comes to mind is to use global state...and I'd rather not do that.
//i don't wanna do this
let condition = true
createButtonStream
.filter(() => condition)
.map(() => condition = false)
.flatMap(() => Api.create())
.onValue(result => {condition = true; doSomething(result)})
In RxJS you use the flatMapFirst or exhaustMap operator (if using RxJS 5)
createButtonStream
.flatMapFirst(() => Api.create())
.subscribe(result => doSomething(result));
flatMapFirst will silently drop events if they arrive before the first source completes, the method should not get invoked.
You can use awaiting. You need a Bus to work around the chicken and egg problem.
const filterBus = new Bacon.Bus()
const createDone = createButtonStream
.filter(filterBus.toProperty(true))
.flatMap(() => Api.create())
const inProgress = createButtonStream.awaiting(createDone)
filterBus.plug(inProgress.not())
createDone.onValue(doSomething)

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

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

How to asynchronously call a database with subrecords using LINQ?

I'm using EF6 and want to make the following query fully asynchronous:
await MyDataContext.ADbSet.
First(a => a.Something == "Something").
ASubCollection.
Select(x => new { x.SubCollectionId }).
ToListAsync();
This doesn't work, I believe due to First() returning the actual entity and access to ASubCollection being an ICollection, not an IQueryable.
I was able to work around this with the following code:
await MyDataContext.ADbSet.
Where(a => a.Something == "Something").
SelectMany(a => a.ASubCollection).
Select(x => new { x.SubCollectionId }).
ToListAsync();
However, this seems "hacky" as I'm using a Where(...) when I should be using a First() as I know at compile time that there will be exactly one element satisfying the query. Is there a better way of doing this?
The call to First() is a call that actually enumerates the underlying sequence and returns an entity instead of a Task. Thus First() won't work together with the await-keyword.
Your second solution is completely valid (and not "hacky" at all) in this context, because there is no need to add a limit to the generated database query, as the Where(...)-call will return exactly one element in this special case - with or without a limit in the query.
If the Where-call is likely to return multiple elements, or you simply want to ensure that there will be only the first element considered, inserting a call to Take(1) will bring the first element of the sequence, but still be an IQueryable:
await MyDataContext.ADbSet
.Where(a => a.Something == "Something")
.Take(1)
.SelectMany(a => a.ASubCollection)
.Select(x => new { x.SubCollectionId })
.ToListAsync();

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