You essentially consume WCF Data Services the same
way you consume pure WCF services. Basically you need to add a service
reference from the client and then instantiate the proxy class. Such a
class will be generated for you by Visual Studio 2010 and will expose
members for accessing data on the server. WCF Data Services can be consumed by different kinds of
clients such as Windows (Console, Windows Forms, WPF) and Web
(Silverlight, ASP.NET Ajax) applications. The next example shows you how
to consume Data Services from a Console client application. Such a
project template is useful for focusing on concepts that you can apply
to other kinds of applications.
Creating a Client Application
The goal of the next example
is to show how you can perform read/insert/update/delete operations
against a Data Service from a client. Follow these steps:
1. | Add to the current solution a new Console project and name it NorthwindClient.
|
2. | Right-click the project name and select Add Service Reference.
This adds a reference to the Data Service similar to what happens for
WCF services. Because in our example the service is available in the
current solution, just click Discover. In real applications you will instead type the Uri of your service. Figure 1 shows how the Add Service Reference dialog appears now.
|
3. | Replace the default identifier in the Namespace textbox with a more appropriate one, such as NorthwindServiceReference; then click OK.
|
Tip
If the Data Service also
exposes service operations (see the next section for details), these
will be listed in the right side of the dialog.
At this point Visual Studio 2010
generates what in WCF is defined as a proxy class, which is client
code. Basically it generates a number of classes: one class that
inherits from System.Data.Services.Client.DataServiceContext and that can be considered as the Astoria counterpart for the Entity Framework’s ObjectContext and a series of counterpart classes for entities in the EDM. This means that, for our example, you have Customer, Order, and Order_Detail classes implemented on the client side. All these classes implement the INotifyPropertyChanged interface so that they can notify the UI of changes on the original data source. Instead the DataServiceContext class also exposes properties of type System.Data.Services.Client.DataServiceQuery(Of T)
that are collections of the previously mentioned classes and that
represent strongly typed queries against entity sets exposed by a Data
Service. For example, the DataServiceContext class in our example (automatically named NorthwindEntities for consistency with the related context in the service) exposes the Customers, Orders, and Order_Details properties, respectively, of type DataServiceQuery(Of Customer), DataServiceQuery(Of Order), and DataServiceQuery(Of Order_Detail).
Although you should never manually edit auto-generated code, if you are
curious, you can inspect previously mentioned classes by expanding the
NorthwindServiceReference item in Solution Explorer and clicking the Reference.vb
file. This is the place where all the client code is implemented. You
notice lots of similarities with an entity data model implementation,
but do not become confused because WCF Data Services are a different
thing. By the way, such similarities can help you understand how to
perform data operations. For example, there are methods for adding
objects (AddToCustomers, AddToOrders) and for removing objects (DeleteObject). For now, add the following Imports directives that allow shortening lines of code:
Imports NorthwindClient.NorthwindServiceReference
Imports System.Data.Services.Client
The next step is instantiating the proxy client class. At module level, add the following declaration:
Private northwind As New _
NorthwindEntities(New _
Uri("http://localhost:1443/NorthwindService.svc"))
Notice how the
instance requires you to specify the service Uri. This is the same that
is specified when adding the service reference. The northwind variable represents the instance of the DataServiceContext
class that exposes members for working against entities exposed by the
Data Service and that allows performing CRUD operations. The first
operation I am going to explain is insertion. Consider the following
function:
Private Function AddNewOrder(ByVal relatedCustomer As Customer) As Order
Dim newOrder As New Order
With newOrder
.Customer = relatedCustomer
.OrderDate = Date.Today
.ShipCountry = "Italy"
.ShipCity = "Milan"
.ShipName = "First"
End With
northwind.AddToOrders(newOrder)
northwind.SetLink(newOrder, "Customer", relatedCustomer)
northwind.SaveChanges()
Return newOrder
End Function
The code first creates an
instance of a new order and populates the desired properties. Notice how
a relationship to the specified customer is also set. This relationship
is just set in-memory, but it needs to be explicitly set when sending
changes to the actual database.
The new order is added to the model via the AddToOrders method, whereas SetLink
explicitly sets the relationship. The method requires the new object as
the first argument, the navigation property in the model as the second
argument, and the master object in the master-details relationship.
Finally the code saves the new data to the database invoking SaveChanges.
Later you see how to send to the data source changes in a batch.
Performing an update operation is an easy task. You simply get the
instance of the desired object and edit properties. The following
snippet demonstrates how to update an existing order:
Private Sub UpdateOrder(ByVal OrderID As Integer)
'Retrieving the one instance of the specified Order with
'a lambda.
Dim ord = northwind.Orders.Where(Function(o) o.OrderID = _
OrderID).First
ord.ShipName = "Second"
ord.ShipCity = "Cremona"
ord.ShipCountry = "Italy"
End Sub
The code shows how you
simply get the instance of your object and replace properties. If you
want to save changes at this point, invoke SaveChanges. We are not doing this now because we will save changes in the batch later.
WCF Data Services do not support First and Single extension methods directly on the data source. This is the reason why in the previous code snippet we had to pass through a Where method.
|
The next step is implementing a deletion method. This is also a simple task, as demonstrated by the following code:
Private Sub DeleteOrder(ByVal OrderID As Integer)
Dim ord = northwind.Orders.Where(Function(o) o.OrderID = _
OrderID).First
northwind.DeleteObject(ord)
End Sub
Also in this case you simply get the instance of the object you want to remove and then invoke the DeleteObject
method. The last step is showing how you can save multiple changes to
entities in one shot. The following code demonstrates this:
Private Sub SaveAllChanges()
northwind.SaveChanges(Services.Client.SaveChangesOptions.Batch)
End Sub
SaveChanges receives an argument of type System.Data.Services.Client.aveChangesOptions, which is an enumeration whose most important value is Batch,
which enables saving all pending changes with a single http request;
thus it is efficient with regard to performances. Now we just need to
invoke the various methods from within the Sub Main. The following code first creates a new order, updates it, and finally deletes it:
Sub Main()
Dim cust = northwind.Customers.Where(Function(c) c.CustomerID = _
"ALFKI").First
Try
Dim anOrder = AddNewOrder(cust)
Console.WriteLine("Added new order: {0}", anOrder.OrderID)
UpdateOrder(anOrder.OrderID)
Console.WriteLine("Updated order {0}. ShipCity now is {1},
ShipName now is {2}",
anOrder.OrderID, anOrder.ShipCity,
anOrder.ShipName)
'Replace the order ID with a valid one
DeleteOrder(anOrder.OrderID)
Console.WriteLine("Order deleted")
SaveAllChanges()
Console.ReadLine()
northwind = Nothing
Catch ex As DataServiceQueryException
Console.WriteLine("The server returned the following error:")
Console.WriteLine(ex.Response.Error.Message)
Console.ReadLine()
Catch ex As Exception
End Try
End Sub
The code also is ready for intercepting a DataServiceQueryException, a particular object that provides client information from DataServiceException
objects thrown on the server side. If you now run the application, you
get messages informing you about the data operations progress, as shown
in Figure 2.
Querying Data
One of the most common
requirements of any data framework is the ability to perform queries.
WCF Data Services allow two modes on the client side. The first one is
utilizing query strings similarly to what it is possible to do with
Uris. To accomplish this you invoke the Execute(Of T) method from the DataServiceContext
class, where T is the type you want to retrieve a collection of. For
example, the following code returns a collection of orders for the
specified customer, sorted by order date:
Dim myOrders = Northwind.Execute(Of Order)(New _
Uri("/Customers('ANATR')/Orders?orderby=OrderDate", _
UriKind.Relative))
This way is efficient but
avoids the strongly typed approach provided by LINQ. Fortunately the
.NET Framework also enables using a special LINQ provider known as LINQ
to Data Services. The following code snippet demonstrates how you can
obtain the same result as previously by writing a LINQ query:
Dim myOrders = From ord In northwind.Orders
Where ord.Customer.CustomerID = "ANATR"
Select ord
Of course this is
powerful but not necessarily the best choice. For example, you might
want to prevent indiscriminate data access from clients, or you might
simply want better performances implementing queries on the server side
and exposing methods returning query results. This is where service operations take place.