You can read Part Ihere.
In the last post I gave an overview of the sample Silverlight + RIA application (code can be found here). For Part 2, I wanted to talk more about the specifics of the service layer and the order of the calls.
You’ll notice the declaration of several data sources in the constructor of the service:
[EnableClientAccess()]
public class TicketService : DomainService
{
private TicketDataSource _ticketDataSource;
private CustomerDataSource _customerDataSource;
private ProductDataSource _productDataSource;
private ActionTypeDataSource _actionTypeDataSource;
private ResultTypeDataSource _resultTypeDataSource;
private TicketActionDataSource _ticketActionDataSource;
public TicketService()
{
_ticketDataSource = new TicketDataSource();
_customerDataSource = new CustomerDataSource();
_productDataSource = new ProductDataSource();
_actionTypeDataSource = new ActionTypeDataSource();
_resultTypeDataSource = new ResultTypeDataSource();
_ticketActionDataSource = new TicketActionDataSource();
}
In our production environment we’re delegating the actual database operations to separate classes that make calls to Oracle Stored Procedures through ADO.NET using an Oracle provider. It doesn’t have to be an Oracle DB of course – you could use any database you like when you just want to hide the details away behind an abstraction such as this. For the sample application, we store the data in memory using the HTTP Session.
Insert: Order Of Operations
What I really wanted to talk about in this post is the order of operations when it comes to doing inserts and saving new entities. For this application we have three pieces of data that can be inserted into the database:
- Ticket
- Customer
- TicketAction
Product, ActionType and ResultType are also data on the Ticket or TicketAction objects, but they are pulled from the database tables as static lookups and only referenced in their parent objects as foreign keys. No new values are inserted into these three tables via the application and that is why they do not have INSERT methods on the TicketService.
Ticket is our root aggregate object. Customer is a child object on the root, and TicketActions are a collection on the root. The order of operations is determined by RIA. InsertCustomer() will be called first, then InsertTicket() and finally, InsertTicketAction(). Let’s see how and why this happens.
Inserting a Customer
All single-entity child objects on the root will have their respective INSERT methods called first. If you think about it, this makes sense. The primary key of each child object is used as a foreign key on the parent object, so the child object primary key has to be known BEFORE the parent object can be saved. Thus, for a new Customer object, we have to first insert it into the DB and return the newly generated CustomerId to the Ticket BEFORE the Ticket gets saved.
public void Insert(Customer customer)
{
customer.CustomerId = _customerDataSource.Insert(customer);
}
So here we’re passing the Customer to a class that will perform the actual INSERT into Oracle, and in turn that method will get the generated PK from Oracle and return it. It is then our responsibility in this call to associate that PK with the Customer object, because the Customer is going to get sent back across the wire to the client when RIA is done making all these calls.
Inserting a Ticket
Once the Customer has been saved to the DB (and the new CustomerId returned and set on the Customer object) then RIA will call the INSERT method for the Ticket itself. At this point in time we need to set the CustomerId (and any other child foreign keys) on the Ticket object:
public void Insert(Ticket ticket)
{
ticket.CustomerId = ticket.Customer.CustomerId;
ticket.TicketId = _ticketDataSource.Insert(ticket);
foreach (var ticketAction in ticket.TicketActions)
{
ticketAction.TicketId = ticket.TicketId;
}
}
And just like the Customer INSERT method, once we save the Ticket to the database we need to get the generated PK back from Oracle and set it on our Ticket.
Inserting TicketActions
The last thing that happens here is the insertion of multiple TicketActions, as this is a child collection on the Ticket object. And again, if you think about it from a DB point of view, this makes sense. Each TicketAction has a foreign key TicketId. Thus, a TicketAction can’t be saved to the DB until the TicketId is known. For a new Ticket, that won’t be until the Ticket is saved and the id returned. Once the Ticket’s primary key (TicketId) is known, we can attach it to each TicketAction and save them as well. RIA will make that call automatically, after the INSERT method for the Ticket is done:
public void InsertTicketAction(TicketAction ticketAction)
{
ticketAction.TicketActionId = _ticketActionDataSource.Insert(ticketAction);
}
Done Inserting
Once all of those calls are made and the various pieces of data have been saved to their respective tables, the root aggregate object is ready to be reloaded by RIA. This will happen AUTOMATICALLY. You do not need to call Load() on the service context again in your ViewModel after you have called SubmitChanges(). RIA will automatically reload your entity client-side depending on what kind of LoadBehavior you specified (for this project we’ve specified a LoadBehavior.RefreshCurrent). This is another reason why it is so important to return the primary keys and set those values on the objects when you saved that data to the database.
Next time I’ll cover UPDATE operations and explain one of the quirks of RIA sending entities down the wire….