Author |
Share Topic Topic Search Topic Options
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Topic: Blocking Async Queries Posted: 10-Mar-2009 at 1:46pm |
Hi,
We're writing a Silverlight application using DevForceEF. We've a number of properties on our classes that are 'derived', meaning they're not directly bound to a column in the database, and they are computed on-the-fly. Here's a bad example of the type of thing I'm talking about:
public partial class Employee { public bool WorksAlone { get { MyEntityManager em = this.EntityManager as MyEntityManager; int count = em.Employees.Where(i => i.Location == this.Location).Count();
return (count == 1); } }
}
The problem is that Silverlight requires the queries be executed asyncronously, which is fine in most cases, but with respect to a derived property like the example above, it becomes tricky. Typically we'd use an IAsyncResult and simply wait until the Async call completed before returning the result, but I can't seem to figure out how to do this with DevForce. The only thing I can seem to make work at all feels really dirty.
public partial class Employee { public bool WorksAlone { get { bool result = false;
MyEntityManager em = this.EntityManager as MyEntityManager; if (em != null) { IEntityQuery<Employee> query = em.Emplyoees.Where(i => i.Location == this.Location);
worksAloneCompleted = false; em.ExecuteQueryAsync(query, new AsyncCompletedCallback<EntityFetchedEventArgs>(WorksAloneCompleted), null); while (!worksAloneCompleted);
result = worksAloneResult; }
return result; } }
private bool worksAloneCompleted = false; private bool worksAloneResult = false; private void WorksAloneCompleted(EntityFetchedEventArgs args) { int count = 0; foreach (var item in args.Result) count++;
worksAloneResult = (count == 1); worksAloneCompleted = true; } }
Do you have any other recommendations how to handle this?
Thanks,
Ken
|
|
smi-mark
DevForce MVP
Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
|
Post Options
Quote Reply
Posted: 10-Mar-2009 at 5:06pm |
Hi,
Could you do something similar to this:
private bool mComputedPropertyInitialized; private int mComputedProperty = -1; public int ComputedProperty { get { if (mComputedPropertyInitialized) { return mComputedProperty; } EntityManager em = this.EntityManager; EntityQuery<User> query = new EntityQuery<User>(); em.ExecuteQueryAsync(query, user_asyncCallback, null); return 0; //or another default value } } private void user_asynccallback(EntityFetchedEventArgs<User> pArgs) { //process result mComputedProperty = 1; mComputedPropertyInitialized = true; this.EntityAspect.ForcePropertyChanged(null); }
That way it will return your default until the async has been completed and then force rebinding with forcepropertychanged when it returns.
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 10-Mar-2009 at 10:55pm |
Although maybe not appropriate here, when the situation allows it and the necessary data is already in cache you can set the QueryStrategy to CacheOnly and execute a synchronous query. When things get messy with async queries, pre-loading can be a good idea.
DevForce also supports asynchronous navigation and returns a PendingEntity or PendingEntityList for navigation properties which must be fulfilled asynchronously. Looks like this feature isn't yet in the Developer's Guide, but check out the Release Notes for more information (the feature was introduced in v4.2.0).
Also, be careful with any waits or sleeps or otherwise blocking the UI thread in Silverlight. Something like
while (!worksAloneCompleted);
could block the thread and leave the SL application hanging.
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 11-Mar-2009 at 3:03am |
Hi smi-mark,
The trouble is, for our use case we're not binding to this property, we're using it in computations, and that computation has to be correct the first time through. That, and the value can change frequently so performing the lazy load and storing a member variable doesn't work for us. So, while in many cases your solution would likely be preferred, it's not really what we're looking to do. Thanks though, I appreciate the feedback!
Edited by ken.nelson - 11-Mar-2009 at 3:05am
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 11-Mar-2009 at 3:08am |
Originally posted by kimj
DevForce also supports asynchronous navigation and returns a PendingEntity or PendingEntityList for navigation properties which must be fulfilled asynchronously. Looks like this feature isn't yet in the Developer's Guide, but check out the Release Notes for more information (the feature was introduced in v4.2.0).
|
I haven't looked into this yet, but hopefully it'll help, I'll update this post if it does. Thanks.
Originally posted by kimj
Also, be careful with any waits or sleeps or otherwise blocking the UI thread in Silverlight. Something like
while (!worksAloneCompleted);
could block the thread and leave the SL application hanging.
|
Yeah, unfortunately it may be what we have to do. This property has to return the correct value on each and every call.
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 11-Mar-2009 at 3:45am |
Originally posted by kimj
DevForce also supports asynchronous navigation and returns a PendingEntity or PendingEntityList for navigation properties which must be fulfilled asynchronously. Looks like this feature isn't yet in the Developer's Guide, but check out the Release Notes for more information (the feature was introduced in v4.2.0).
Also, be careful with any waits or sleeps or otherwise blocking the UI thread in Silverlight. Something like
while (!worksAloneCompleted);
could block the thread and leave the SL application hanging. |
The PendingEntityList will definitely help for other properties, that's a good bit of info, can I suggest adding that to the developer's guide?
What actually looks like the fix I was looking for was also in the release notes for v4.2.0, so thanks for pointing me to that document:
For those needing additional control over their asynchronous operations the EntityManager also supports the IAsyncResult asynchronous pattern through an explicit implementation of the IEntityManagerAsync interface. You will need to cast an EntityManager to this interface in order to use methods following this pattern. In the IAsyncResult pattern an asynchronous operation is implemented as two methods named BeginOperationName and EndOperationName to begin and end the asynchronous operation "OperationName". More information on using this interface is available in the DevForce Framework Help reference.
|
This allows us to block the async in a much cleaner way.
IEntityQuery<Employee> query = em.Employee.Where(i => i.Location == this.Location); IAsyncResult result = (em as IEntityManagerAsync).BeginExecuteQuery<Employee>(query, null, null);
while (!result.IsCompleted);
IEnumerable<Employee> x = (em as IEntityManagerAsync).EndExecuteQuery<Employee>(result);
return (x.Count() == 1);
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 11-Mar-2009 at 9:36am |
Glad the IEntityManagerAsync might be useful. Although, I'm still leery that these blocking calls will block the entire UI and effectively crash the browser. Have you used them successfully in SL?
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 6:08am |
Originally posted by kimj
Glad the IEntityManagerAsync might be useful. Although, I'm still leery that these blocking calls will block the entire UI and effectively crash the browser. Have you used them successfully in SL? |
We've not yet tried the above solution in SL, our entire office is currently having some difficulty getting DevForce EF running properly using SL (See http://www.ideablade.com/forum/forum_posts.asp?TID=1127 if you're interested in that). Once we can successfully run, I'll post an update.
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 1:12pm |
Ok, we've now got our machines working using SL, so I was finally able to test this, and you're right in that it locks up the browser, but I'm still confused as to why. I would expect it to lock up for the period of time that it took to complete the async query, in which case, we'd work out some method of throwing up a progress updater, but it seems to just lock up entirely. I guess we're just going to have to live with not having the derived properties.
One follow on question about Navigation Properties, we have a need to iterate over the collection that the navigation property would return, but obviously navigation properties are also async, so we can't really do that until the async completes. I can't seem to figure out how to hook in a 'Completed' callback to a navigation property. I tried doing something like the following but got a compile error:
IEntityQuery query = customer.Orders;
em.ExecuteQueryAsync(query, QueryCompleted, null);
I then tried the following, but got a runtime error:
IEntityQuery query = customer.Orders.ToQuery();
em.ExecuteQueryAsync(query, QueryCompleted, null);
The only way I could work it out was to do the following:
IEntityQuery query = em.Orders.Where(i => i.CustomerID == customer.UniqueID);
em.ExecuteQueryAsync(query, QueryCompleted, null);
Basically, I've got to ignore that a navigation property exists, and just control it manually. Is there a better way to handle this?
Thanks,
Ken
|
|
smi-mark
DevForce MVP
Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 1:36pm |
Is there no way you can wait till the derived property has populated and then call an event?
Ie, have a "wait" form in your SL app while it waits for the event to fire before continuing on?
Also, on your initial call you can do a span using .Include on the query
IEntityQuery query = em.Customers.Where(c => c.Id == 1).Include(Customer.OrdersEntityProperty.Name);
Then when the results come back you will have the orders.
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 2:23pm |
Ken,
All navigation in Silverlight will be done asynchronously if the data isn't already in cache. There's a property on the EntityManager called UseAsyncNavigation which for SL is always on. This means that something like
customer.Orders
will automatically be resolved asynchronously if necessary. You can use the IsPendingEntityList property on the RelatedEntityList to tell you whether "real" or "pending" data has been returned.
var orders = customer.Orders;
Assert.IsTrue(orders.IsPendingEntityList); orders.PendingEntityListResolved += (o, e) => {
// Now have real data
Assert.IsFalse(orders.IsPendingEntityList);
}
For scalar properties you'd use IsPendingEntity and PendingEntityResolved.
It looks like we need to trap and disallow attempts to call ExecuteAsyncQuery on the navigation properties, since as you found this doesn't work.
|
|
kimj
IdeaBlade
Joined: 09-May-2007
Posts: 1391
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 2:27pm |
Oh, regarding why the wait blocks the UI - you can take that up with Microsoft :-). In order to avoid blocking the browser, they disallow any type of wait on the UI thread in Silverlight. A little frustrating I know, since this same logic works fine in WPF and WinForms applications.
|
|
smi-mark
DevForce MVP
Joined: 24-Feb-2009
Location: Dallas, Texas
Posts: 343
|
Post Options
Quote Reply
Posted: 12-Mar-2009 at 2:31pm |
If i need something right away i usuall use include but there are a few methods of doing it as Kim has said.
This gets the customer and the orders belong to the customer before returning.
em.Customers.Where(c => c.Id ==1).Include(Customer.OrdersEntityProperty.Name).ExecuteAsync( args => { if (args.Error != null) { throw args.Error; } else { Customer cust = args.Result.First(); MessageBox.Show(cust.Orders.Count.ToString()); } }, null);
This gets the customer, returns with the customer object, and then gets the orders.
em.Customers.Where(c => c.Id == 1).ExecuteAsync( args => { if (args.Error != null) { throw args.Error; } else { Customer cust = args.Result.First(); if (cust.Orders.IsPendingEntityList) { cust.Orders.PendingEntityListResolved += (o,args2) => { MessageBox.Show(args2.ResolvedEntities.Count.ToString()); }; } } }, null); }
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 13-Mar-2009 at 7:14am |
Originally posted by smi-mark
Is there no way you can wait till the derived property has populated and then call an event?
Ie, have a "wait" form in your SL app while it waits for the event to fire before continuing on?
|
Probably, though it will take a bit of reworking our current app to do so. Really a big part of the reason I posted this message was to determine what is and isn't possible, so this discussion has been a big help. We'll just have to rethink the way we're doing certain things.
Originally posted by smi-mark
Also, on your initial call you can do a span using .Include on the query
IEntityQuery query = em.Customers.Where(c => c.Id == 1).Include(Customer.OrdersEntityProperty.Name);
Then when the results come back you will have the orders.
|
I agree that using .Include() should solve a lot of our problems, what I'm uncertain about with that though is the accuracy if multiple concurrent users are adding/removing customer orders and say the property on Customer is an Order count. Any idea?
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 13-Mar-2009 at 7:17am |
Originally posted by kimj
Ken,
All navigation in Silverlight will be done asynchronously if the data isn't already in cache. There's a property on the EntityManager called UseAsyncNavigation which for SL is always on. This means that something like
customer.Orders
will automatically be resolved asynchronously if necessary. You can use the IsPendingEntityList property on the RelatedEntityList to tell you whether "real" or "pending" data has been returned.
var orders = customer.Orders;
Assert.IsTrue(orders.IsPendingEntityList); orders.PendingEntityListResolved += (o, e) => {
// Now have real data
Assert.IsFalse(orders.IsPendingEntityList);
}
For scalar properties you'd use IsPendingEntity and PendingEntityResolved.
|
That's exactly what I was looking for. I'm not sure how I missed the PendingEntityListResolved delegate. Thanks!
|
|
ken.nelson
Groupie
Joined: 03-Mar-2009
Location: Ann Arbor, MI
Posts: 54
|
Post Options
Quote Reply
Posted: 13-Mar-2009 at 7:20am |
Originally posted by kimj
Oh, regarding why the wait blocks the UI - you can take that up with Microsoft :-). In order to avoid blocking the browser, they disallow any type of wait on the UI thread in Silverlight. A little frustrating I know, since this same logic works fine in WPF and WinForms applications. |
Thanks. :) I was more surprised than anything, like you stated, the same logic works fine in WPF and WinForms.
|
|