Service Operations in WCF Data Services allow you to create WCF style methods on your service. In general it is good practice to do most of your CRUD operations directly on the entities exposed by your WCF Data Services model but every once in a while you may have a need to do some custom operation that isn’t covered by the model and this is where service operations come into the picture. 

Below is a simple service operation called CustomersByCity the returns a set of customers whose city matches the one passed as a parameter to the method:

[WebGet]
public IQueryable<Customer> CustomersByCity(string city)
{
    if (city == null)
        throw new DataServiceException(400, "You must specify a city name");

    return from c in CurrentDataSource.Customers
           where c.City == city
           select c;
}

Now, the goal of this blog post is not to talk in depth about Service Operations but instead to talk about how they can be used by the WCF Data Services client. The .NET and Silverlight clients include a nice code generation feature that can be pointed at an existing service and will generate a DataServiceContext class that can do CRUD operations against the entities exposed by the service. The generated code will include a class for each entity exposed by the service as well as properties on the generated DataServiceContext class that access each entity set. 

What is not included, however, are methods on the generated context for accessing the Service Operations exposed by the service. For instance, if you have the service operation shown above defined on your service, you might expect to be able to type context.CustomersByCity(“Madrid”) and have that service operation executed by the client and get back all the customers with City == “Madrid”. This is a limitation of the code generation in the current WCF Data Services stack and one that we hope to address in a future release. For now, though, you can supplement this by adding new methods to the generated class yourself.

 Using CreateQuery for Service Operations

The example below shows how to add a method “CustomersByCity” to the partial class NorthwindEntities that was generated by the code generation utility via the Add Service Reference wizard in Visual Studio. The method creates a DataServiceQuery for the service operation and automatically adds the “city” query option that the service operation requires. The query will not be immediately executed and it is possible to further compose onto the query. If additional Data Service query options are added to the query, the whole query will be sent to the server and executed there.

namespace NorthwindClient.Northwind
{
    public partial class NorthwindEntities
    {

    public DataServiceQuery<Customer> CustomersByCity(string city) 
    { 
       if (city == null) 
          throw new DataServiceClientException("CustomersByCity requires a city name");

       return this.CreateQuery<Customer>("CustomersByCity").AddQueryOption("city","'"+city+"'"); 
    }

    }
}

The following code snippet shows how to use this method. Note that the Take(2) method is added to the query and sent to the server, this means that the result from the server only includes the first two results:

NorthwindEntities svc = new NorthwindEntities(new Uri("http://hostname/Northwind.svc/"));

var q = from c in svc.CustomersByCity("Madrid").Take(2) select c ; 
foreach (Customer c in q) 
       Console.WriteLine(c.CustomerID);

For Silverlight users, this method will work well in pure async environment of Silverlight because it allows you to call the BeginExecute method on the result of the CustomerByCity method.

Using Execute() for Service Operations

There is one scenario where using CreateQuery to execute a service operation won’t work. When the service operation returns a singleton (single entity or single primitive/comlex type) and the service operation has one or more parameters. The server requires that the parenthesis after the service operation name in the URI are not specified if the service operation returns a singleton and the CreateQuery method will always add parenthesis when a query option is added to the service operation. To get around this issue, it is possible to use the Execute method to execute the service operation. The example below shows how to acheive the same result as the example above, this time using the DataServiceContext.Execute() method.

The example generates the URI to pass to the Execute method on the DataServiceContext and also automatically adds the “city” query option that the service operation requires. The URI is then passed to Execute and the result is returned out of the method. The interesting thing to note is that the result of this method is an IEnumerable which implies two things 1. the query is executed against the service when this method is called and 2. any further composition on the query will be done in-memory on the client.

namespace NorthwindClient.Northwind
{
    public partial class NorthwindEntities
    {
        public Customer CustomerByCity(string city)
        {
            if (city == null)
                throw new DataServiceClientException("CustomersByCity requires a city name");

            Uri uri = new Uri("/CustomersByCity()?city='" + city + "'", UriKind.Relative);

            return this.Execute<Customer>(uri);
        }

    }
}

The following code snippet shows how to use this method. Note that the Take(2) method is executed in memory on the client and the result from the service includes ALL customers with city = “Madrid”:

        NorthwindEntities svc = new NorthwindEntities(new Uri("http://hostname/Northwind.svc/"));

       var q = svc.CustomersByCity("Madrid").Take(2) ;
       foreach (Customer c in q)
           Console.WriteLine(c.CustomerID);

 

Shayne Burgess
Program Manager II
WCF Data Services