[Edit: The problem of eager-loading the stuff I ask for in expand is solved]
I didn't remember when first writing the code and this post that I've got a DataLoadOptions override in a custom context wrapped around the original Linq to SQL model. The Catalog/Items association in the example below is never eager-loaded, so of course I have to add a LoadWith(catalog => catalog.Items) option on the innermost context's DataLoadOptions. Now my .Expand("Items") call gives me a larger set of data and I'm most happy. Just feeling quite stupid not knowing my own API. :P
The code now looks like the attractive:
DataServiceQuery<Catalog> catalogQuery = context.Catalogs
.Expand("Items/ItemFields/TemplateField")
.Where(c => c.ParentId == (int)rootCatalogId) as DataServiceQuery<Catalog>;
catalogQuery.BeginExecute(
delegate(IAsyncResult result)
{
catalogs = catalogQuery.EndExecute(result).ToList();
AllDone();
}, null);
The original solution was quite fun to write tho:
[Warning: See bolded text below]
I've been fiddling a bit with an ADO Data Service and the expand method. Can't seem get it to work, or I don't get to know when the associations are loaded.
So I started looking into LoadProperty, but Silverlight wants to call everything asynchronously, so if I loop in the callbacks, I just generate a lot more callbacks, and never know when the last one occurs.
Enter the NotifyingStack. :P
public class NotifyingStack<T> : Stack<T>
{
public event EventHandler OnPopped;
public NotifyingStack(EventHandler popped) : base()
{
OnPopped = popped;
}
public new T Pop()
{
T obj = base.Pop();
if (OnPopped != null)
OnPopped(this, EventArgs.Empty);
return obj;
}
}
Now I can do this:
private void PopulateSubCatalogsWithItems(object rootCatalogId)
{
DataServiceQuery<Catalog> catalogQuery = context.Catalogs
.Expand("Items/ItemFields")
.Where(c => c.ParentId == (int)rootCatalogId) as DataServiceQuery<Catalog>;
justAStack = new NotifyingStack<object>(new EventHandler(OnPop));
justAStack.Push(null);
catalogQuery.BeginExecute(
delegate(IAsyncResult catalogQueryResult)
{
IEnumerable<Catalog> catalogResult = catalogQuery.EndExecute(catalogQueryResult);
catalogs = catalogResult.ToList();
foreach (Catalog catalog in catalogs)
{
justAStack.Push(null);
context.BeginLoadProperty(catalog, "Items",
delegate(IAsyncResult itemQueryResult)
{
IEnumerable<Item> items = syzwebContext.EndLoadProperty(itemQueryResult).Cast<Item>();
foreach (Item item in items)
{
justAStack.Push(null);
syzwebContext.BeginLoadProperty(item, "ItemFields",
delegate(IAsyncResult itemFieldResult)
{
syzwebContext.EndLoadProperty(itemFieldResult);
justAStack.Pop();
}, null);
}
justAStack.Pop();
}, null);
}
justAStack.Pop();
}
, catalogQuery);
}
private void OnPop(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(justAStack.Count);
if (justAStack.Count == 0)
{
AllDone();
}
}
I was first experimenting with a timer checking a regular stack, but when trying to add stuff to the UserControl, Silverlight threw a cross threading exception.
In this case, the event handler is in the right thread, and as long as I'm diciplined with the pushing and popping, the AllDone method will only be called once when all the data is loaded. :)
Now, if anyone have a good laugh because you know how to get the expand method to instruct the BeginExecute method to get a big package, ie. by altering the server side service, please gimme a comment. (I've been looking at the xml from the ADO Service, and it only generates urls to the data for the expanded properties)
Turns out though, I need a lot more than the stuff I got so far. So I have to keep this little trick up my sleave for something else. This method would create about a 100 small chatty calls to the server before done, hence I'm gonna [Edit: Remember my DataLoadOptions in the future]. :)
(But it was real fun doing it)