I have a ViewModel in which I would like to listen for changes to items in two ReactiveLists, Payments and AccountPayments. The Lists are instantiated and the ChangeTrackingEnabled is set to true:
this.Payments = new ReactiveList<transaction.PaymentViewModel>();
this.Payments.ChangeTrackingEnabled = true;`
this.AccountPayments = new ReactiveList<AccountPaymentViewModel>();
this.AccountPayments.ChangeTrackingEnabled = true;`
Then, defined in my ViewModel, I have an ObservableAsPropertyHelper readonly property:
readonly ObservableAsPropertyHelper<decimal> _totalPayments;
public decimal TotalPayments { get { return _totalPayments.Value; } }
My intent is to set TotalPayments whenever any of these items change in both lists. I tried using WhenAny:
this.WhenAny(vm => vm.Payments.ItemChanged,
vm => vm.AccountPayments.ItemChanged,
(a, b) => a.Sender.Payments
.Where(x => x.Amount.HasValue).Sum(x => x.Amount.Value)
+ b.Sender.AccountPayments
.Where(x => x.Amount.HasValue).Sum(x => x.Amount.Value))
.ToProperty(this, vm => vm.TotalPayments, out _totalPayments);
While this compiles fine, it doesn't seem to catch the changes.
I also tried using WhenAnyObservable:
this.WhenAnyObservable(
vm => vm.Payments.ItemChanged,
vm => vm.AccountPayments.ItemChanged)
.Select(_ =>
this.Payments.Where(x => x.Amount.HasValue)
.Sum(x => x.Amount.Value)
+ this.AccountPayments.Where(x => x.Amount.HasValue)
.Sum(x => x.Amount.Value))
.ToProperty(this, vm => vm.TotalPayments, out _totalPayments);
But this won't compile.
Is there a way to accomplish what I am trying to do? Any help would be greatly appreciated.
The first won't work as it's observing property changes and ItemChanged itself won't change, it's an observable.
The second is pretty much correct, but requires a bit of a modification. WhenAnyObservable requires that all the observables are the same type. As you're uninterested in the actual result, you can select Unit and merge the two:
this.WhenAnyObservable(a => a.Payments.ItemChanged).Select(_ => Unit.Default)
.Merge(this.WhenAnyObservable(a => a.AccountPayments.ItemChanged).Select(_ => Unit.Default));
You can't select Unit.Default within WhenAnyObservable as it re-writes this expression to observe the property changes to make sure it has the latest observable. If neither Payments nor AccountPayments will change (i.e. they're read only), you can omit the WhenAnyObservable altogether:
Payments.ItemChanged.Select(_ => Unit.Default)
.Merge(AccountPayments.ItemChanged.Select(_ => Unit.Default));
Related
I have a class ClassWithProperty that contains a property, SomeProperty, that is an instance of another class, ClassWithEvent, with an event, SomeEvent. The ClassWithEvent object will regularly change. I need ClassWithProperty to keep a single updated subscription to the event in ClassWithEvent.
I've come up with one way to do it so far:
public ClassWithProperty()
{
IDisposable subscription = null;
this
.WhenAnyValue(x => x.SomeProperty)
.Do(x => subscription?.Dispose())
.Where(x => x != null)
.Subscribe(x => subscription =
Observable.FromEventPattern(x, nameof(x.SomeEvent))
.Subscribe(y => SomeMethod()));
}
This solution does work but it somehow does not feel like idiomatic Reactive code, and it also seems somewhat verbose for what I think should be a fairly typical problem to solve. Is there a better way to solve this sort of issue in Reactive?
It's very simple with a Switch operator that switches to the latest produced inner observable of a IObservable<IObservable<T>>. It disposes of the previous subscriptions and only gives you values from the latest observable.
IDisposable subscription =
this
.WhenAnyValue(x => x.SomeClass)
.Select(x => Observable.FromEventPattern(x, nameof(x.SomeEvent)))
.Switch()
.Subscribe(x => SomeMethod());
If SomeClass can be null (which your original code suggests) then you can do this:
IDisposable subscription =
this
.WhenAnyValue(x => x.SomeClass)
.Select(x =>
x == null
? Observable.Never<EventPattern<object>>()
: Observable.FromEventPattern(x, nameof(x.SomeEvent)))
.Switch()
.Subscribe(x => SomeMethod());
I have something like this:
this.WhenAnyValue(x => x.SearchText)
.Where(text => !string.IsNullOrempty(text))
.Throttle(TimeSpan.FromSeconds(0.25))
.InvokeCommand(searchCmd);
Where SearchText is an string property bindied to a textbox and searchCmd is a ReactiveCommand.
What I want is, if searchCmd is already running, the observable should wait. For example, if the user typed "gener" and after an small pause he types "ally", the command will be still running, and will not execute for "generally", only "gener".
Any idea?
EDIT: What I'm doing (and it looks that it works):
First I Added an OAPH:
readonly ObservableAsPropertyHelper<bool> isCmdRunning;
And a property:
public bool IsCmdRunning => isCmdRunning.Value;
Then, in the cosntructor:
this.searchCmd.IsExecuting
.ToProperty(this, x => x.IsCmdRunning, out isCmdRunning);
And finally:
this.WhenAnyValue(x => x.SearchText, x => x.IsCmdRunning, (search, running) => new {Search = search, Running = running})
.Where(value => !string.IsNullOrempty(value.Search) && !value.Running)
.Throttle(TimeSpan.FromSeconds(0.25))
.Select(value => value.Search)
.InvokeCommand(searchCmd);
But I don't know if there is a better way.
I think it is a good solution using CancellationToken.
The Search command:
Search = ReactiveCommand.CreateFromObservable(
() => Observable
.StartAsync(ct => SearchImpl(ct))
.TakeUntil(CancelSearch));
The CancelSearch command:
CancelSearch = Command.Create(() => {}, Search.IsExecuting);
And finally:
this.WhenAnyValue(x => x.SearchText)
.Do(_ => Observable.Return(Unit.Default).InvokeCommand(CancelSearch))
.Throttle(TimeSpan.FromSeconds(0.25))
.InvokeCommand(searchCmd);
I am using NHibernate with mapping by code.
I have three models: Solution, Installation and System. There are one-to-many relations between them. So that each Solution has a list of Installations, and each Installation has a list of Systems.
Each system has a property "Type", which can be "1" or "0".
I am trying to write a method in the Solution repository that will return all the Solutions, with their Installations with only the Systems of type "1".
I have tried the Where-keyword in the SystemMap but i get the same result with and without it. Then i tried a few different experiments with QueryOver(???) without success.
How do i go about to filter on information in the last node?
Thank to your answer, i have done the following implementation, but it results in a huge amount of Systems and Solutions. Maybe i have done something wrong?
The Maps are as follows:
public SAPSolutionMap()
{
Id(t => t.YPID);
Property(e => e.ShortName);
Property(e => e.FullName);
Bag(x => x.SapInstallations, colmap =>
{
colmap.Table("SAPInstallation");
colmap.Key(x => x.Column("Solution"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
colmap.Cascade(Cascade.None);
}, map => map.OneToMany(m => m.Class(typeof(SAPInstallation))));
}
public SAPInstallationMap()
{
Id(t => t.InstallationNumber);
Bag(x => x.SapSystems, colmap =>
{
colmap.Table("sapgui");
colmap.Key(x => x.Column("Installation"));
colmap.Inverse(true);
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Cascade(Cascade.None);
colmap.Fetch(CollectionFetchMode.Join);
//colmap.Where("Type = 1");
}, map => map.OneToMany(m => m.Class(typeof(SAPSystem))));
ManyToOne(x => x.SapSolution, map =>
{
map.Column("Solution");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPSolution));
});
}
public SAPSystemMap()
{
Id(t => t.ID, t => t.Generator(Generators.Identity));
Property(e => e.Type);
Property(e => e.ExplanationText);
ManyToOne(x => x.SapInstallation, map =>
{
map.Column("Installation");
map.NotNullable(true);
map.Cascade(Cascade.None);
map.Class(typeof(SAPInstallation));
});
}
And the Query:
public IList<SAPSolution> GetProductionSystems()
{
SAPSystem syst = null;
SAPInstallation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.SapInstallation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.SapSolution.YPID);
// main Query
var query = Session.QueryOver<SAPSolution>()
.WithSubquery
.WhereProperty(root => root.YPID)
.In(subquery);
return query.List<SAPSolution>();
}
Thank you!
General solution should be:
// this is a subquery (SELECT ....
System syst = null;
Installation installation = null;
var subquery = QueryOver.Of(() => syst)
.JoinQueryOver(x => x.Installation, () => installation)
.Where(() => syst.Type == 1)
.Select(x => installation.Solution.ID)
;
// main Query
var query = session.QueryOver<Solution>()
.WithSubquery
.WhereProperty(root => root.ID)
.In(subquery)
;
var list = query
.Take(10)
.Skip(10)
.List<Solution>();
What we can see, that Solution, Installation and System
System has property Installation (many-to-one)
Installation has property Solution (many-to-one)
This is expect-able, because it goes side by side with one-to-many (it is the reverse mapping)
So, then we create subquery, which returns just solution ID's which belong to system with searched Type.
Main query is flat (the great benefit) and we can use paging on top of it.
We would be able to do that even if there is only one way (one-to-many). But that will generate more complicated SQL query ... and does not make sense. In C# we can have both relations...
EXTEND:
You did a great job. Your mapping and query is really cool. But there is one big but: LAZY is what we should/MUST use. Check this:
NHibernate is lazy, just live with it, by Ayende
So, our, collections cannot be FETCHING with a JOIN, because that will multiply the result (10 solutions * 100 installation * 10 systems == 10000 results)
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.NoLazy);
colmap.Fetch(CollectionFetchMode.Join);
We should use LAZY as possible. To avoid later 1 + N issue, we can use batch-fetching (for example check this)
How to Eager Load Associations without duplication in NHibernate?
So, our collections should be mapped like this:
Bag(x => x.SapSystems, colmap =>
{
...
// THIS IS not good way
colmap.Lazy(CollectionLazy.Lazy);
colmap.BatchSize(100);
With this setting, the query will really use only the root object and related collections will be loaded very effectively
I have a data model like this
I would like to load all the related entities from a Reconciliation into a Reconciliation object.
For now the only way I could find to load all the related entites to a single Recon is in multiple Lists. But I want to load every related entities in a Reconciliation object. If possible in an elegant way.
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485).First();
List<ReconciliationDetail> reconDetails = recon.ReconciliationDetails.ToList();
List<JrnlEntryDetail> jrnlDetails = reconDetails.Select(r => r.JrnlEntryDetail).ToList();
List<JrnlEntry> jrnl = jrnlDetails.Select(j => j.JrnlEntry).ToList();
List<ARInvoice> invoices = jrnl.SelectMany(j => j.ARInvoices).ToList();
List<ARInvoiceDetail> invoicesDetail = invoices
.SelectMany(i => i.ARInvoiceDetails).ToList();
List<ARCredMemo> credmemos = jrnl.SelectMany(j => j.ARCredMemoes).ToList();
List<ARCredMemoDetail> credmemosDetail = credmemos
.SelectMany(c => c.ARCredMemoDetails).ToList();
List<IncomingPay> incomingPays = jrnl.SelectMany(j => j.IncomingPays).ToList();
List<IncomingPayDetail> incomingPaysDetail = incomingPays
.SelectMany(i => i.IncomingPayDetails).ToList();
// ... and so on for outgoing pays, AP Invoices AP Cred Memo ...etc
I have also tried to load it with Include and Select but I get this exception :
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
And I don't get how I could load every childs of JrnlEntry using Include and Select
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485)
.Include(r => r.ReconciliationDetails
.Select(d => d.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry)
.SelectMany(j => j.ARInvoices).SelectMany(i => i.ARInvoiceDetails))
Edit
Managed to do it this way too but it's not very beautiful :
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485)
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.ARInvoices.Select(i => i.ARInvoiceDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.ARCredMemoes.Select(c => c.ARCredMemoDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.IncomingPays.Select(i => i.IncomingPayDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.OutgoingPays.Select(o => o.OutgoingPayDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.APInvoices.Select(o => o.APInvoiceDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.APCredMemoes.Select(o => o.APCredMemoDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.JrnlEntryDetails))
There are two ways to perform Eager Loading in Entity Framework:
ObjectQuery.Include Method
Explicit loading using either DbEntityEntry.Collection Method or DbEntityEntry.Reference Method along with their respective Load methods
There are also manners to write Raw SQL Queries against database:
DbSet.SqlQuery Method deals with entities
Database.SqlQuery Method deals with arbitrary types
Database.ExecuteSqlCommand Method for arbitrary DDL/DML
For the case, when you're attempting to load nearly entire database, it would be good idea to execute dedicated store procedure against it.
Try with just .Include(r => r.ReconciliationDetails) initially. Then add the .Select() statements one-by-one. At what point does the exception reappear? The .SelectMany() call looks a bit suspicious to me!
A second question that might help identify the problem... After you run the code that contains all the ToList() calls, is your recon entity complete? i.e. are all its navigation properties populated? This should be the case because of the automatic 'fixup' behavior of Entity Framework.
With EF, sometimes it is more efficient to load a complex object graph with several calls rather than chained Include() calls. Check the generated SQL and see what is most efficient in your case.
Not sure if it's to late but could you benefit from structuring your code such as
var acctName = "someName";
var detailList = _repository.Include(e => e.JrnlEntryDetail).Filter(c => c.JrnlEntryDetail.Any(e => e.AcctName == acctName)).Get().ToList();
I have a ReactiveUI-like view model. It has several properties of different types which fire NotifyPropertyChanged events and I want to subscribe a method that will be called when any is fired, but I am not interested in actual values.
My current code is a bit ugly (due to opaque true selects). Is there a way to express this which shows the intention of just caring when the event occurs?
this.ObservableForProperty(m => m.PropertyOne)
.Select(_ => true)
.Merge(this.ObservableForProperty(m => m.PropertyTwo).Select(_ => true))
.Subscribe(...)
I am merging about 8 properties, so it's more ugly than shown.
Since this looks like ReactiveUI, how about using the WhenAny operator:
this.WhenAny(x => x.PropertyOne, x => x.PropertyTwo, (p1, p2) => Unit.Default)
.Subscribe(x => /* ... */);
In general though, if you were combining arbitrary Observables, you could also write this a bit more clearly using the non-extension method:
Observable.Merge(
this.ObservableForProperty(x => x.PropertyOne).Select(_ => Unit.Default),
this.ObservableForProperty(x => x.PropertyTwo).Select(_ => Unit.Default),
this.ObservableForProperty(x => x.PropertyThree).Select(_ => Unit.Default)
).Subscribe(x => /* ... */);
Also, if you're subscribing to every property of a ReactiveObject, it's probably better to just use:
this.Changed.Subscribe(x => /* ... */);
You could make it an extension method to make the intent clearer:
public static IObservable<bool> IgnoreValue<T>(this IObservable<T> source)
{
return source.Select(_ => true);
}
...
this.ObservableForProperty(m => m.PropertyOne).IgnoreValue()
.Merge(this.ObservableForProperty(m => m.PropertyTwo).IgnoreValue())
.Subscribe(..);