LoadPropertyMarkDirty does not consider BypassPropertyChecks

LoadPropertyMarkDirty does not consider BypassPropertyChecks

Old forum URL: forums.lhotka.net/forums/t/12840.aspx


jayoub posted on Thursday, March 05, 2015

Hi,

I have a data bound tree with check boxes on a screen that allows the user to set properties on quite a lot of business objects (up to 100, or maybe more). If a parent node is checked, it sets boolean properties on all of the business objects bound to the child nodes to true or false accordingly.

I set up these properties following the CSLA pattern, but when I use SetProperty in the property setters on these business objects, it causes a performance issue. If a parent node is checked, it takes more than 10 seconds for the UI to respond, because SetProperty is being called on so many business objects.

I have tried using LoadProperty instead, as I do not have any business rules associated with these properties, but LoadProperty does not mark the business object dirty or run change notification.

I have also tried LoadPropertyMarkDirty, while also manually calling OnPropertyChanging and OnPropertyChanged so that change notification is run. This cuts down the UI response time to 1/4 of when I use SetProperty.

The issue with calling LoadPropertyMarkDirty is that I have found it does not take BypassPropertyChecks into account.

When I'm calling Fetch on the business objects, and using BypassPropertyChecks, when the property value is changed to a value different from the default value, the business object gets marked dirty when LoadPropertyMarkDirty is called.

When using SetProperty, it takes BypassPropertyChecks into account so that, when properties change during a fetch, the business object is not marked dirty. On the other hand, LoadPropertyMarkDirty will still mark the object dirty while using BypassPropertyChecks. This is not the behavior I expected, as it seems to be different than how SetProperty operates in regards to marking the object dirty.

I have tried to somehow manually override the default CSLA behavior, but the problem is that the _bypassPropertyChecks field in Core.BusinessBase is marked private, so I don't have the ability to make a kind of custom behavior based around its value.

If anyone has any ideas to make SetProperty perform faster or get around my issue with LoadPropertyMarkDirty, it would be appreciated.

Thanks,

Jonathan

ajj3085 replied on Thursday, March 05, 2015

BypassPropertyChecks exists to prevent rules from running (auth and business) while you are loading your BO, as well as prevent the object from being marked dirty.  It effectively does what LoadProperty does.  LoadPropertyMarkDirty is supposed to mark the object dirty, while not running the business rules.  So LoadPropertyMarkDirty is in fact doing what it is designed to do.

First off, I suspect your object design is off; editing 100+ objects at once seems a bit off and more like it should be handled by a command object which hits the DB.  If there are complex rules that need to be enforced, maybe you have no other choice.

What I would do is go back to SetProperty, but before you go looping through the objects call SuspendBinding on all your binding sources, loop and change, then call ResumeBinding.  You may also need to call ResetBinding to cause the data to refresh.

This will stop the UI from trying to react to each individual property change while letting your rules run normally and setting Dirty correctly as well.  So that is my first guess is that all the events being raised are forcing the UI to refresh itself (including repaint), which is really slow.  Telling the UI to ignore things for a while generally fixes these issues.

The other two usual culprits in my experience are business rules that hit the DB or complex code that fires in internal BO event handling (for example, doing complex things in the root objects OnChildChanged method).  If you have business rules that must hit the UI, I'm not sure you can do much there except ensure the entire thing runs as quickly as possible, but since you probably have at least one network hop you may be limited by what you can do.  Usually if OnChildChanged is the issue its along similar lines; causing DB hits when the code runs.  If that is the case you can try setting lists RaiseLIstChangedEvents to false while you set your values, then restore it to true and fire a non-specific property changed / list changed.  Usually you'd want to do this via a method on your list or BO so that the UI needn't worry about those details.

jayoub replied on Friday, March 06, 2015

Thank you for your responses. I'm using CSLA 4.5, XAML and MVVM.

Andy

First off, I suspect your object design is off; editing 100+ objects at once seems a bit off and more like it should be handled by a command object which hits the DB.  If there are complex rules that need to be enforced, maybe you have no other choice.

I'm not sure that my object design is off. I have a hierarchy of objects four levels deep. Each object in the hierarchy has a list of children objects and a boolean property which I am binding to. In the UI, I am using a data tree control with check boxes. Basically, the "IsChecked" property of the check box is bound to the boolean property on the business object.

I'm not sure that a command object would help me here, because that would be when I need to run a command on the database. What I need in this situation is a business object that the UI can interact with.

The datatree gives the user the ability to make a really large amount of changes by clicking on a parent node. When a parent node is checked, that cascades down to all of the child nodes, and they set the boolean properties on each node along the way, calling SetProperty in the property setters. I'm thinking that calling SetProperty so many times as a result of checking one checkbox is what is leading to the performance issue.

I wasn't aware of the purpose of LoadPropertyMarkDirty, but was thinking that it may give me the ability to change a property value while marking the object dirty as well as avoid some of the overhead of SetProperty. It won't work for me because it marks my object dirty when I fetch an object from the database.

One thing I do know is that if I set my property setters from SetProperty to LoadProperty, the UI responds instantly when I check a parent node. SetProperty can make the UI take more than 10 seconds to respond.

None of my boolean properties have any business rules associated with them, so I know that's not the issue. I also don't think that the UI responding to property changing events is the only culprit. I'll try to run a performance profiler and find exactly where the performance hit is occurring. The reality may be that the user is just making a very large amount of changes with one action, and that I need to pop up a window asking them to wait while it is being processed.

ajj3085 replied on Friday, March 06, 2015

Your design may not be off; usually though when a user has 100+ Bos on the screen at once, it means a lot of data, and huge amounts of data are hard for a user to effectively work with.  So when I see that happening it helps to at least question if the design is correct.  Sounds like its just a tree of BOs which a simple Boolean, so that doesn't sound unreasonable, but only you will know for sure since you have all the details of the use case.

Johnny is right in that the only way to really see what's going on is with a profiler, but if you don't have access to one we can make some reasonable guesses.

Given your post though, its almost certain that this is just a UI problem, and you're right in that SetProperty is probably causing hundreds (or more) of PropertyChanged events, which XAML responds to and just like WinForms ultimately causes repainting, among other things.  All that is very slow, but this is a common problem in UI frameworks, so while I don't know the specifics for XAML I'm sure there's a way to suspend the binding, set the IsChecked on the BO and let it do the cascade, and then resume binding.  You'd be surprised how slow just the UI eventing can be; this alone may solve your problem

You don't mention that anything else happens in your BOs when this Boolean is set, so I'm assuming that the only effect of setting it is that it goes and sets the flag on its immediate children.  If that's not the case, you may have other rules firing too, and if any hit the DB that will be a performance issue.

The command object comes into play if the use case really is as simple as you suggest (check a box, and that value cascades to all decedents).  What you could do is have a RO list that you use to get the initial tree structure, then create an tree structure of non-Csla View models that have the checkbox and probably the text you display at each node.  This would be a simple POCO really that probably implements IPropertyChanged.  (SO you'll probably still need to do the suspend / resume binding).  When the user hits save you have a command object which takes the ID of a node in the DB and runs a SQL query to update it and all children, probably using a CTE (common table expression) to do this in one DB query.

The advantage of that approach is that you use a lightweight RO object tree to initially get the tree data, the VMs you bind to your UI aren't Csla classes and so don't have the overhead of editable Csla business objects, and then the command object will issue one simply query to hit all the rows instead of multiple queries to update each row.  But again this depends on your use case and if there's any other additional complexity I don't know about that would make editable BOs the way to go with this.

At any rate, I'd start with investigating how to suspend / resume binding in XAML temporarily and see if that alone is enough to fix the issue.

jayoub replied on Monday, March 09, 2015

I was able to profile the CSLA dll and it looks like the property change events are in fact the culprit. When BusinessBase runs CheckPropertyRules, it changes a metastate property (I'm assuming IsValid or something like that), which called OnPropertyChanged. It also runs MarkDirty, which also changes a metastate property (probably IsDirty), which calls OnPropertyChanged. So I'm certain the UI responding to the events is where the bottleneck is.

I think that I can remove the bindings on my controls in the code behind of the View responding to the CheckedChanging event or something like that, and then set the binding again in the CheckedChanged event. I don't know exactly how that will be achieved, but I know that I need to remove the binding somehow before the property change events are thrown, and then put the binding back after they are done. 

No matter how that is implemented, it should solve my performance problem.

ajj3085 replied on Monday, March 09, 2015

Your solution sounds like it should work.  Perhaps this SO answer has the key: http://stackoverflow.com/a/3383010/347348

 

using(var d = Dispatcher.DisableProcessing())
{
   
/* your work... Use dispacher.begininvoke... */
}

JonnyBee replied on Thursday, March 05, 2015

Hi,

Maybe you could provide us with more information about your application, CSLA version and UI type.

First of all - I totally agree with what Andy wrote.
BypassPropertyChecks is only effective on SetProperty/SetPropertyConvert to make this method work like LoadProperty/LoadPropertyConvert.

LoadPropertyMarkDirty was created for use by the RuleEngine to update fields in the BO and make these dirty if changed. The rule engine will raise OnPropertyChanged as appropriate after all rules have run.

Maybe you could provide us with more information about your application, CSLA version and UI type.

CSLA 4.x has default property changed mode to Xaml. If you are using WinForms make sure to use the BindingList type list classes and set PropertyChangedMode to Windows.

protected virtual void CheckPropertyRules(IPropertyInfo property)
{
  var propertyNames = BusinessRules.CheckRules(property);
  if (ApplicationContext.PropertyChangedMode == ApplicationContext.PropertyChangedModes.Windows)
    OnPropertyChanged(property);
  else
    foreach (var name in propertyNames)
      OnPropertyChanged(name);
}

CSLA 4.x rule engine is quite a bit smarter and you may also specify (to some extend) when rules are allowed to run, see base class PropertyRule.

protected PropertyRule()  : base()
{
  CanRunAsAffectedProperty = true;
  CanRunOnServer = true;
  CanRunInCheckRules = true;
}

Also be aware that the rule engine will process rules in 2 rounds

  1. Rules where the property is "PrimaryProperty"
  2. Rules for all "AffectedProperties" in step 1 + rules for Properties where "ChangedProperty" in InputProperty.

This may or may not make the rule engine run some expensive rules.

I do recommend to first revert back to use SetProperty and then use a performance profiler to determine where the time is being spent.

Is it running rules, list changed event handlers, UI code or other parts of the code. Identify the root cause and then find the proper solution.

Do you have a sample project that demonstrates the issue?

Copyright (c) Marimer LLC