3. Using OData on the Phone
One of the unique challenges of the phone is
to create efficient access to data supported by services. Public
services such as Twitter, eBay, and others have already defined the
types of services they are supporting. OData is one of those formats
you might find yourself supporting if you want to consume data from a
provider that supports it. You also might decide to expose your own
data via OData when you want to expose it to the phone. The main reason
OData is a compelling option on the phone is that it enables you to be
very specific with how you want to query the data. Being able to tune
an application and return only the data (for instance, fields) required
using the $select
query option means you can be very efficient when accessing data via the phone.
Another reason using OData with the phone is
compelling is that it is supported by a rich client-side model to the
data you are dealing with. This is especially helpful if you need to be
able to support data modification. The OData client for the phone
supports a client-side context object that tracks changed objects for
you automatically and enables you to batch those changes back to the
server.
OData supports a variety of platforms (PHP,
iOS, .NET, and Java), and support for OData is included natively in the
Windows Phone SDK.
4. Generating a Service Reference for OData
In the Web Services section, you used Visual
Studio to add a service reference, which built an object model for the
service. Creating a service reference to an OData feed is accomplished
in the same way. You simply use the Add Service Reference option on
your project). When the Add Service Reference dialog box
appears, you insert an OData feed (or click Discover for data services
in your own project), as shown in Figure 1.
FIGURE 1 Adding a service reference to an OData feed
Much like adding a service
reference to a web service, adding a service reference to an OData feed
requires a feed location (for instance, http://www.nerddinner.com/Services/OData.svc).
When you press the Go button, it retrieves the metadata about the feed
to enable you to create the reference classes to the OData feed. Adding
this service reference not only creates classes for the different
entities on the OData feed, but also creates a context class that is
used when interacting with the OData feed. By using this context class,
you can retrieve and update data in the OData feed (assuming it
supports updating). Let’s see how that is accomplished.
5. Retrieving Data
To start accessing data with OData, you must
create an instance of the context class. This class gives you access to
the different endpoints in the service. Depending on the service, this
class name could end in “Entities” or “Context.” For example, in the
NerdDinner OData service, it is called NerdDinnerEntities
.
This context class (and the other classes generated by the service
reference) are contained in a namespace specified by the OData service. Figure 2 shows adding the DataServices
namespace to have access to the entity class. The name of this
namespace was specified in the Add Service Reference dialog box (shown
in Figure 1).
FIGURE 2 Adding a using statement to the data service
Creating an instance of the context class requires that you include the address of the service you are querying, like so:
// The Service Address
var svcUri = new Uri("http://www.nerddinner.com/Services/OData.svc");
// Create the Context
NerdDinnerEntities ctx = new NerdDinnerEntities(svcUri);
This context class enables you to track the date you get back from the server, but the DataServiceCollection
class is at the center of how you will query data. The DataServiceCollection
class is a generic collection that supports the INotifyCollectionChanged
interface so it easily supports data binding. You effectively can ask
the collection to load itself with a query, as shown here:
// Craft the Query
var query = from d in ctx.Dinners
where d.HostedBy == "Shawn Wildermuth"
orderby d.Title
select d;
// Create a collection to hold the results
DataServiceCollection<Dinner> results = new
DataServiceCollection<Dinner>();
// Handle the completed event to do error handling
results.LoadCompleted += (s, e) =>
{
if (e.Error != null)
{
MessageBox.Show("An error occurred");
}
};
// Load the Collection using the Query
results.LoadAsync(query);
// Bind it to the UI
theList.ItemsSource = results;
Although you could craft the query directly via
the URI syntax, the OData libraries for the phone enable you to use
LINQ to describe your query, which it automatically converts to the URI
query syntax for you. Next, the code creates a new instance of the DataServiceCollection
that expects it to be filled with Dinner
objects. Then it handles the collection’s LoadCompleted
event to handle any errors. And finally it calls LoadAsync
to actually start the operation asynchronously. At that point, you can
bind the collection to the user interface, and the query will execute
and fill in the DataServiceCollection
in the background. After the collection is filled, the data binding will take over and display the results.
If you are using projections in your queries,
the results will continue to be the full objects but only the requested
fields will be filled in. For example, if you request only some of an
entity’s properties, it will return just those properties as shown here:
// Craft the Query
var query = from d in ctx.Dinners
where d.Country == "USA"
orderby d.Title
select new Dinner()
{
DinnerID = d.DinnerID,
Title = d.Title,
HostedBy = d.HostedBy
};
If you are using expansion in your queries, the
results will continue to be the main objects you’ve requested, but the
navigated objects will be serialized as part of your query. For
instance, adding an expansion (via the Expand
method) will eager-load any related entities:
// Craft the Query
var query = from d in ctx.Dinners.Expand("RSVPs")
where d.Country == "USA"
orderby d.Title
select d;
6. Updating Data
Querying the OData feed via the DataServiceCollection
and context classes enables you to track any changes to the collection so that as objects are changed that exist in the DataServiceCollection
, those objects are marked as changed. In addition, if you want to delete an item, you can simply remove it from the DataServiceCollection
.
Removing the item from the collection marks it as a deleted element.
And finally, you can add items to the collection to mark them as new
(or inserted) objects. You can see examples of this here:
// Changing items from the collection marks them as changed
Dinner dinner = _dinnerCollection[0];
dinner.EventDate = DateTime.Today;
// Adding Items marks them as New (Inserted)
Dinner newDinner = new Dinner()
{
EventDate = DateTime.Today,
HostedBy = "ShawnWildermuth"
};
_dinnerCollection.Add(newDinner);
// Removing Items marks them as Deleted
Dinner oldDinner = _dinnerCollection[1];
_dinnerCollection.Remove(oldDinner);
As changes are tracked, you can update those
changes with the server (assuming you have permission to update the
server) by calling BeginSaveChanges
on the context object. Remember, even though the DataServiceCollection
is the holder of the objects, ultimately it’s the context object that actually tracks them so that when you call BeginSaveChanges
it will batch these changes back to the server, like so:
// Save the changes
_theContext.BeginSaveChanges(new AsyncCallback(r =>
{
// Response is a collection of errors
DataServiceResponse response = _theContext.EndSaveChanges(r);
// If there are any errors, show message
if (response.Any(op => op.Error != null))
{
MessageBox.Show("Sorry, update failed.");
}
}), null);
Unlike reading data using OData, the saving of changes uses an older asynchronous style using an AsyncCallback
object. The code can be simplified (as shown here) by using a lambda to handle the changes inline, but it still requires the BeginSaveChanges
and EndSaveChanges
methods to be called. The DataServiceResponse
object returned at the end of the save operation is a collection of OperationResponse
objects. These OperationResponse
objects contain the exception that was encountered on the server. In this instance, the LINQ Any
operator checks whether any of the responses have an error, which is the Exception
. If the collection is empty or responses do not contain errors, you can assume that the operation completed successfully.
OData and Transactions