I’m working on an Silverlight 4.0 application that spans several technologies and this time I’m in charge of the “server side”, in specific I’m developing the WCF RIA Service that will be consumed by the main application and in these weeks I have to admit that, while RIA Services are a real boost in productivity and allows you to forget all low level WCF stuff, you can sometime waste a lot of time figuring why basic things don’t work as expected, so the truth is: If you need to develop a production application you’d better understand how things work under the hood.
I’ve hit several walls during service development, and I’ve learnt many things about RIA services, if you need a good starting point Mike Taulty has a series of very good posts about RIA Services inner workings.
On this post I’d like to point out one of the various issues I’ve found, other might come in future posts. (time permitting)
An example is the best way to describe the scenario: I have a DomainService that exposes a collection of cars, and since cars can be bought the service also allows me to get the list of orders placed by the user.
Let’s start with the entities and since database technology is not important (you can use any data access technology you love with RIA Services BTW…) all server side data on following example will be code generated:
[DataContract]
public class Car
{
[DataMember]
[Key]
public int Key { get; set; }
[DataMember]
public string Plate { get; set; }
[DataMember]
public int OrderId { get; set; }
}
[DataContract]
public class Order
{
[DataMember]
[Key]
public int Key { get; set; }
[DataMember]
public string OrderName { get; set; }
[DataMember]
[Include]
[Association("Ordercars", "Key", "OrderId")]
public List<Car> OrderCars { get; set; }
}
If you already know WCF RIA Services everything should look familiar including the Include and Association stuff required to expose children entities from Order class.
Let now write some client side code that consumes domainService’s methods:
private void LoadCars()
{
this.context.Load(this.context.GetCarsQuery(), lop =>
{
List<Car> cars = lop.Entities.ToList();
Debug.WriteLine("--Cars--");
foreach (var car in cars)
{
Debug.WriteLine(car.Plate);
}
}, null);
}
private void LoadOrders()
{
this.context.Load(this.context.GetOrdersQuery(), lop =>
{
List<Order> orders = lop.Entities.ToList();
Debug.WriteLine("--Orders--");
foreach (var order in orders)
{
Debug.WriteLine(order.OrderName);
foreach (var car in order.OrderCars)
{
Debug.WriteLine(car.Plate);
}
}
}, null);
}
again, just a couple of methods that consume both methods and write returned entities to output window…
Let’s suppose now that the application initially loads all cars and lately asks for all orders placed by the user, something we can easily simulate using:
private void button1_Click(object sender, RoutedEventArgs e)
{
LoadCars();
LoadOrders();
}
easy enough right?
let’s run it and let’s see what we get on Visual Studio output window…
Hey,
where are order’s cars?
You might think about something wrong somewhere in your code but the truth is that everything is correct indeed, need an hint?, just comment LoadCars invocation and here’s what we get:
voilà, our cars are there… so what’s not good?
This is just one of the things that might hurt you if you don’t know the inner workings of RIA services: Normally, when you have some entities (Cars in our example) inside the context and you retrieve those entities again (uniquely identified by the property marked with the Key attribute) RIA services just discard them since already available, and this is for some scenario good but in our case the cars exposed as Order’s children have a OrderId property that is different than one on entities actually on local context, and since the OrderId is the association’s foreign key the association can’t be reconstructed on client side and that explains why we don’t get any car exposed by retrieved order.
Ok, so what we can do? Use a less known option available on Load method: LoadBehavior
LoadBehavior allows to define how entities on context must be treated when a new entity gets downloaded from the server again, among available options there’s a RefreshCurrent that means, more or less: “When an entity already available inside local context get re-downloaded since it might have been updated update local entity with the values of new one”
Lets’ fix our problem:
private void LoadOrders()
{
this.context.Load(this.context.GetOrdersQuery(), LoadBehavior.RefreshCurrent, lop =>
{
List<Order> orders = lop.Entities.ToList();
Debug.WriteLine("--Orders--");
foreach (var order in orders)
{
Debug.WriteLine(order.OrderName);
foreach (var car in order.OrderCars)
{
Debug.WriteLine(car.Plate);
}
}
}, null);
}
and here’s the output:
Once again, just a simple detail, but it might require you spending a lot of time trying to figuring what’s wrong (and nothing was indeed) that’s why I suggest you to study WCF RIA Services in detail before using them in a real project.
…till next time, have a happy year’s ending!