Recommended way to load child properties or work with collections in Silverlight

Recommended way to load child properties or work with collections in Silverlight

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


wjcomeaux posted on Monday, May 03, 2010

Due to the async requirement of Silverlight we have had to figure out a way to load child properties in Silverlight. What we are currently doing is basically loading the child properties asynchronously but making the call to the property behave in a synchronous manner. This works but I am not happy with the solution.

Another issue we struggle with is working with collections in an async manner. If you have a foreach loop on an editable collection how do you handle this? We are basically doing pretty much the same as when loading the property in the code below (making the foreach act synchronous in that nothing else gets processed on the thread until the foreach is done). This completely removes any async benefits we would have, and again, I'm not hapy with it.

Can you all share ideas of how you are handling these scenarios? We definitely want to keep the object graph as is so loading other root objects asynchronously is not an option for us.

Thanks, Will

//Our Current Property Loading Code
private
bool _privilegeAsyncPropertyIsLoading = false;
private static readonly PropertyInfo< Privilege > _privilegeAsyncProperty = RegisterProperty< Privilege >(p => p.PrivilegeAsync, Csla.RelationshipTypes.Child);
public Privilege PrivilegeAsync
{
  get
  {
    System.Threading.
AutoResetEvent waitEvent = new System.Threading.AutoResetEvent(false);
    if (!_privilegeAsyncPropertyIsLoading && !FieldManager.FieldExists(_privilegeAsyncProperty))
    {
      _privilegeAsyncPropertyIsLoading =
true;
      Privilege.GetPrivilegeAsync(PrivilegeID, (o, e) =>
      {
        LoadProperty(_privilegeAsyncProperty, e.Object);
        OnPropertyChanged(_privilegeAsyncProperty.Name);
        _privilegeAsyncPropertyIsLoading =
false;
        waitEvent.Set();
      });
    }
    else
    {
      waitEvent.Set();
    }
    waitEvent.WaitOne();
    return GetProperty(_privilegeAsyncProperty);
  }
}

 

RockfordLhotka replied on Monday, May 03, 2010

I would suggest stepping back from the problem and looking at it anew.

Silverlight server communication is async. This has a very real impact on how applications should be constructed. In many cases there are different ways to approach the overall problem that recognize the async reality and make use of it rather than fighting it.

What you are doing right now is fighting the async model, and while you might win the fight you probably won't be real happy in the end.

The nature of async is that you need to shift work from a synchronous flow into an async flow. Usually this means doing much of the actual work in a callback instead of in-line.

For UI-centric scenarios this is easy, because the UI is already event-driven and CSLA will make things "just work". Lazy load a property, and the data will just appear in the UI when it is available. That's automatic, thanks to CSLA interacting correctly with data binding to make it happen.

For algorithmic processing (like your foreach loop) you are really faced with a choice - remain linear and fight the async reality, or rethink your algorithm to exploit the async model - which means breaking apart the "start the process" from the "do the work" parts of your algorithm.

You should also (I think) question the use of lazy loading as a general rule, especially if you have algorithmic code that relies on the data. Why would you lazy load it if you know you need it? Lazy loading is intrinsically less efficient, and should only be used if the data is rarely actually loaded.

But even if you say that the algorithmic operation is rarely invoked, so lazy loading "makes sense", I would suggest that you consider this operation to be a separate use case - at least think through that possibility. Because if it is a separate use case, then you could just load the data using a different root object. You still need the algorithm to be modeled as async, but it is simpler to put the foreach loop in the FetchCompleted handler of the data portal than what you are doing now.

Finally, if you say none of this works - you really do need lazy loading of a property of an existing root object. OK, that's fine. Then what you need is an event that is raised when the property has actually been loaded. You can use PropertyChanged, but it might be simpler to declare your own property and raise it when the async load is complete.

That allows the calling code (where your foreach is now) to trigger the property load, and then you'd put the foreach loop itself into a handler of that lazy-load-complete event.

In the final analysis, you need to look at breaking your algorithm up to be async-aware, and trigger the foreach part based on the completion of the async load.

wjcomeaux replied on Monday, May 03, 2010

We have two reasons why we are using Lazy Loading.
1) Our object graphs can be fairly complex with some items only being used some of the time.
For instance, using a typical (very simple) User/Role scenario:
User
   RoleList
     PrivilegeList
We have various use cases like (User Edit, User RoleList Edit, User PrivilegeList Edit) and all of these are under the context of the user.
Given that we have ONLY one User object (we are using CodeSmith and generating with a customized version of their latest CSLA templates), I don't have just a User object that doesn't know about RoleList (as would be the proper case that I think you would recommend). So, we are kind of forced into a lazy-load scenario because when all we want to do is edit a User we don't want to wait for Roles and Privileges to load.

2) Our choice of code generation templates comes with it's own set of limitations. Generating the above code means our User object has a RoleList collection exposed as a public property AND our RoleList exposes a public property of User. We had to do some customizations to the templates to prevent infinite load loops because we ran into this scenario quite often:  Load User -> Load RoleList -> Load UserList for each Role in RoleList -> "to infinity and beyond". This customization basicaly came in the form of lazy loading so that Many-to-one and Many-to-many properties only get loaded when they are called the first time and on update (to prevent saving an out of date version). We have overridden the IsValid check to basically do (if RoleList == null || RoleList.IsValid) to allow for the lazy load. The rationale is that if the child object is null then accept it as valid because it wasn't employed for the current use case. This is all exposed by our customized code generation templates.
  
On the topic of asynchronously loading properties, how do you accomplish this? Our sample that I gave in my initial post forces the async property to behave synchronously from the POV of the calling code. This was adopted because you can't provide a callback to a Property. Would you recommend exposing an PropertyXYZLoaded event be exposed on the business object that would get raised from inside of the property and have the calling code listen for the event? Or would it be better to expose LoadPropertyXYZ(callback) functions for each property and then have the property simply act as as accessor that can and will be null if the LoadPropertyXYZ hasn't been called yet?

Any solution we decide on has to be something that can be rolled into the code generation templates, which isn't usually an issue, just something to keep in mind.

Thanks for all of your help!
Will

RockfordLhotka replied on Monday, May 03, 2010

wjcomeaux

On the topic of asynchronously loading properties, how do you accomplish this? Our sample that I gave in my initial post forces the async property to behave synchronously from the POV of the calling code. This was adopted because you can't provide a callback to a Property. Would you recommend exposing an PropertyXYZLoaded event be exposed on the business object that would get raised from inside of the property and have the calling code listen for the event? Or would it be better to expose LoadPropertyXYZ(callback) functions for each property and then have the property simply act as as accessor that can and will be null if the LoadPropertyXYZ hasn't been called yet?

As I mentioned, if the lazy loading is done within the context of a bound UI, you don't need to worry about making the property appear to be synchronous. The UI will bind to the default null value, and then will update to show the real data when it arrives. This is automatic, and is a good solution for most UI binding scenarios.

The worst thing you might need to do is use a ValueConverter or viewmodel property to allow the UI to disable the area where the child object's data will appear until the value is something other than null. But that's not hard, and is really a UI concern more than a business layer concern. The point is that CSLA objects already expose everything necessary to make this work.

If you want, almost as a bonus, you can add calls to set the IsBusy property of the parent in your async lazy load code. In most of my UIs this would cause a busy animation to run, and would block out the UI from user interaction during the async process - which may or may not be desired.

In any case, using blocking techniques like you have in your code should be avoided. This is because blocking the SL UI thread locks the browser. The whole browser. The user won't be able to switch tabs, resize, move, etc. In short, you'll really irritate your users - I know that sort of experience would make me very unhappy. This is, in fact, why SL defaults to async server access - because the alternative is simply unacceptable for normal web apps, and if Microsoft had made sync possible/easy then everyone would hate Silverlight (they wouldn't blame your app - they'd just blame Silverlight...)

Copyright (c) Marimer LLC