Calling Service Operations (WCF Data Services)

The Open Data Protocol (OData) defines service operations for a data service. WCF Data Services enables you to define such operations as methods on the data service. Like other data service resources, these service operations are addressed by using URIs. A service operation can return collections of entity types, single entity type instances, and primitive types, such as integer and string. A service operation can also return null (Nothing in Visual Basic). The WCF Data Services client library can be used to access service operations that support HTTP GET requests. These kinds of service operations are defined as methods that have the WebGetAttribute applied. For more information, see Service Operations (WCF Data Services).

Service operations are exposed in the metadata returned by a data service that implements the OData. In the metadata, service operations are represented as FunctionImport elements. When generating the strongly-typed DataServiceContext, the Add Service Reference and DataSvcUtil.exe tools ignore this element. Because of this, you will not find a method on the context that can be used to call a service operation directly. However, you can still use the WCF Data Services client to call service operations in one of these two ways:

  • By calling the Execute method on the DataServiceContext, supplying the URI of the service operation, along with any parameters. This method is used to call any GET service operation.

  • By using the CreateQuery method on the DataServiceContext to create a DataServiceQuery object. When calling CreateQuery, the name of the service operation is supplied to the entitySetName parameter. This method returns a DataServiceQuery object that calls the service operation when enumerated or when the Execute method is called. This method is used to call GET service operations that return a collection. A single parameter can be supplied by using the AddQueryOption method. The DataServiceQuery object returned by this method can be further composed against like any query object. For more information, see Querying the Data Service (WCF Data Services).

Considerations for Calling Service Operations

The following considerations apply when using the WCF Data Services client to call service operations.

  • When accessing the data service asynchronously, you must use the equivalent asynchronous BeginExecute/EndExecute methods on DataServiceContext or the BeginExecute/EndExecute methods on DataServiceQuery.

  • The WCF Data Services client library cannot materialize the results from a service operation that returns a collection of primitive types.

  • The WCF Data Services client library does not support calling POST service operations. Service operations that are called by an HTTP POST are defined by using the WebInvokeAttribute with the Method="POST" parameter. To call a service operation by using an HTTP POST request, you must instead use an HttpWebRequest.

  • You cannot use CreateQuery to call a GET service operation that returns a single result, of either entity or primitive type, or that requires more than one input parameter. You must instead call the Execute method.

  • Consider creating an extension method on the strongly-typed DataServiceContext partial class, which is generated by the tools, that uses either the CreateQuery or the Execute method to call a service operation. This enables you to call service operations directly from the context. For more information, see the blog post Service Operations and the WCF Data Services Client.

  • When you use CreateQuery to call a service operation, the client library automatically escapes characters supplied to the AddQueryOption by performing percent-encoding of reserved characters, such as ampersand (&), and escaping of single-quotes in strings. However, when you call one of the Execute methods to call a service operation, you must remember to perform this escaping of any user-supplied string values. Single-quotes in URIs are escaped as pairs of single-quotes.

Examples of Calling Service Operations

This section contains the following examples of how to call service operations by using the WCF Data Services client library:

  • Calling Execute<T> to Return a Collection of Entities

  • Using CreateQuery<T> to Return a Collection of Entities

  • Calling Execute<T> to Return a Single Entity

  • Calling Execute<T> to Return a Primitive Value

  • Calling a Service Operation that Returns No Data

  • Calling a Service Operation Asynchronously

Calling Execute<T> to Return a Collection of Entities

The following example calls a service operation named GetOrdersByCity, which takes a string parameter of city and returns an IQueryable:

' Define the service operation query parameter.
Dim city As String = "London"

' Define the query URI to access the service operation with specific 
' query options relative to the service URI.
Dim queryString As String = String.Format("GetOrdersByCity?city='{0}'", city) _
                            & "&$orderby=ShippedDate desc" _
                            & "&$expand=Order_Details"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

Try

    ' Execute the service operation that returns all orders for the specified city.
    Dim results = context.Execute(Of Order)(New Uri(queryString, UriKind.Relative))

    ' Write out order information.
    For Each o As Order In results
        Console.WriteLine(String.Format("Order ID: {0}", o.OrderID))
        For Each item As Order_Detail In o.Order_Details
            Console.WriteLine(String.Format(vbTab & "Item: {0}, quantity: {1}", _
                item.ProductID, item.Quantity))
        Next
    Next
Catch ex As DataServiceQueryException
    Dim response As QueryOperationResponse = ex.Response

    Console.WriteLine(response.Error.Message)
End Try
// Define the service operation query parameter.
string city = "London";

// Define the query URI to access the service operation with specific 
// query options relative to the service URI.
string queryString = string.Format("GetOrdersByCity?city='{0}'", city)
    + "&$orderby=ShippedDate desc"
    + "&$expand=Order_Details";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

try
{
    // Execute the service operation that returns all orders for the specified city.
    var results = context.Execute<Order>(new Uri(queryString, UriKind.Relative));

    // Write out order information.
    foreach (Order o in results)
    {
        Console.WriteLine(string.Format("Order ID: {0}", o.OrderID));

        foreach (Order_Detail item in o.Order_Details)
        {
            Console.WriteLine(String.Format("\tItem: {0}, quantity: {1}",
                item.ProductID, item.Quantity));
        }
    }
}
catch (DataServiceQueryException ex)
{
    QueryOperationResponse response = ex.Response;

    Console.WriteLine(response.Error.Message);
}

In this example, the service operation returns a collection of Order objects with related Order_Detail objects.

Using CreateQuery<T> to Return a Collection of Entities

The following example uses the CreateQuery to return a DataServiceQuery that is used to call the same GetOrdersByCity service operation:

' Define the service operation query parameter.
Dim city As String = "London"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

' Use the CreateQuery method to create a query that accessess
' the service operation passing a single parameter.       
Dim query = context.CreateQuery(Of Order)("GetOrdersByCity") _
        .AddQueryOption("city", String.Format("'{0}'", city)).Expand("Order_Details")

Try
    ' The query is executed during enumeration.
    For Each o As Order In query
        Console.WriteLine(String.Format("Order ID: {0}", o.OrderID))
        For Each item As Order_Detail In o.Order_Details
            Console.WriteLine(String.Format(vbTab & "Item: {0}, quantity: {1}", _
                item.ProductID, item.Quantity))
        Next
    Next
Catch ex As DataServiceQueryException
    Dim response As QueryOperationResponse = ex.Response
    Console.WriteLine(response.Error.Message)
End Try
// Define the service operation query parameter.
string city = "London";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

// Use the CreateQuery method to create a query that accessess
// the service operation passing a single parameter.       
var query = context.CreateQuery<Order>("GetOrdersByCity")
    .AddQueryOption("city", string.Format("'{0}'", city))
    .Expand("Order_Details");

try
{
    // The query is executed during enumeration.
    foreach (Order o in query)
    {
        Console.WriteLine(string.Format("Order ID: {0}", o.OrderID));

        foreach (Order_Detail item in o.Order_Details)
        {
            Console.WriteLine(String.Format("\tItem: {0}, quantity: {1}",
                item.ProductID, item.Quantity));
        }
    }
}
catch (DataServiceQueryException ex)
{
    QueryOperationResponse response = ex.Response;

    Console.WriteLine(response.Error.Message);
}

In this example, the AddQueryOption method is used to add the parameter to the query, and the Expand method is used to include related Order_Details objects in the results.

Calling Execute<T> to Return a Single Entity

The following example calls a service operation named GetNewestOrder that returns only a single Order entity:

' Define the query URI to access the service operation, 
' relative to the service URI.
Dim queryString As String = "GetNewestOrder"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

Try
    ' Execute a service operation that returns only the newest single order.
    Dim o As Order = _
        context.Execute(Of Order)( _
            New Uri(queryString, UriKind.Relative)).FirstOrDefault()

    ' Write out order information.
    Console.WriteLine(String.Format("Order ID: {0}", o.OrderID))
    Console.WriteLine(String.Format("Order date: {0}", o.OrderDate))
Catch ex As DataServiceQueryException
    Dim response As QueryOperationResponse = ex.Response

    Console.WriteLine(response.Error.Message)
End Try
// Define the query URI to access the service operation, 
// relative to the service URI.
string queryString = "GetNewestOrder";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

try
{
    // Execute a service operation that returns only the newest single order.
    Order order
        = (context.Execute<Order>(new Uri(queryString, UriKind.Relative)))
        .FirstOrDefault();

    // Write out order information.
    Console.WriteLine(string.Format("Order ID: {0}", order.OrderID));
    Console.WriteLine(string.Format("Order date: {0}", order.OrderDate));
}
catch (DataServiceQueryException ex)
{
    QueryOperationResponse response = ex.Response;

    Console.WriteLine(response.Error.Message);
}

In this example, the FirstOrDefault method is used to request only a single Order entity on execution.

Calling Execute<T> to Return a Primitive Value

The following example calls a service operation that returns a single string value:

// Define the query URI to access the service operation, 
// relative to the service URI.
string queryString = "GetCustomerNames";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

try
{
    // Execute a service operation that returns only the newest single order.
    IEnumerable<string> customerNames
        = context.Execute<string>(new Uri(queryString, UriKind.Relative));
    
    foreach (string name in customerNames)
    {
        // Write out order information.
        Console.WriteLine(name);
    }
}
catch (DataServiceQueryException ex)
{
    QueryOperationResponse response = ex.Response;

    Console.WriteLine(response.Error.Message);
}

Again in this example, the FirstOrDefault method is used to request only a single string value on execution.

Calling a Service Operation that Returns No Data

The following example calls a service operation that returns no data:

' Define the query URI to access the service operation, 
' relative to the service URI.
Dim queryString As String = "ReturnsNoData"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

Try
    ' Execute a service operation that returns void.
    context.Execute(Of String)( _
        New Uri(queryString, UriKind.Relative))
Catch ex As DataServiceQueryException
    Dim response As QueryOperationResponse = ex.Response

    Console.WriteLine(response.Error.Message)
End Try
// Define the query URI to access the service operation, 
// relative to the service URI.
string queryString = "ReturnsNoData";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

try
{
    // Execute a service operation that returns void.
    context.Execute<string>(new Uri(queryString, UriKind.Relative));
}
catch (DataServiceQueryException ex)
{
    QueryOperationResponse response = ex.Response;

    Console.WriteLine(response.Error.Message);
}

Because not data is returned, the value of the execution is not assigned. The only indication that the request has succeeded is that no DataServiceQueryException is raised.

Calling a Service Operation Asynchronously

The following example calls a service operation asynchronously by calling BeginExecute and EndExecute:

' Define the service operation query parameter.
Dim city As String = "London"

' Define the query URI to access the service operation with specific 
' query options relative to the service URI.
Dim queryString As String = String.Format("GetOrdersByCity?city='{0}'", city) _
        & "&$orderby=ShippedDate desc" _
        & "&$expand=Order_Details"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

' Define the delegate to callback into the process
Dim callback As AsyncCallback = AddressOf OnAsyncExecutionComplete

' Execute the service operation that returns 
' all orders for the specified city.
Dim results = context.BeginExecute(Of Order)( _
    New Uri(queryString, UriKind.Relative), _
    callback, context)
// Define the service operation query parameter.
string city = "London";

// Define the query URI to access the service operation with specific 
// query options relative to the service URI.
string queryString = string.Format("GetOrdersByCity?city='{0}'", city)
    + "&$orderby=ShippedDate desc"
    + "&$expand=Order_Details";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

// Execute the service operation that returns 
// all orders for the specified city.
var results = context.BeginExecute<Order>(
    new Uri(queryString, UriKind.Relative),
    OnAsyncExecutionComplete, context);
Private Shared Sub OnAsyncExecutionComplete(ByVal result As IAsyncResult)

    ' Get the context back from the stored state.
    Dim context = TryCast(result.AsyncState, NorthwindEntities)

    Try
        ' Complete the exection and write out the results.
        For Each o As Order In context.EndExecute(Of Order)(result)
            Console.WriteLine(String.Format("Order ID: {0}", o.OrderID))

            For Each item As Order_Detail In o.Order_Details
                Console.WriteLine(String.Format(vbTab & "Item: {0}, quantity: {1}", _
                    item.ProductID, item.Quantity))
            Next
        Next
    Catch ex As DataServiceQueryException
        Dim response As QueryOperationResponse = ex.Response
        Console.WriteLine(response.Error.Message)
    End Try
End Sub
private static void OnAsyncExecutionComplete(IAsyncResult result)
{
    // Get the context back from the stored state.
    var context = result.AsyncState as NorthwindEntities;
    
    try
    {
        // Complete the exection and write out the results.
        foreach (Order o in context.EndExecute<Order>(result))
        {
            Console.WriteLine(string.Format("Order ID: {0}", o.OrderID));

            foreach (Order_Detail item in o.Order_Details)
            {
                Console.WriteLine(String.Format("\tItem: {0}, quantity: {1}",
                    item.ProductID, item.Quantity));
            }
        }
    }
    catch (DataServiceQueryException ex)
    {
        QueryOperationResponse response = ex.Response;

        Console.WriteLine(response.Error.Message);
    }
}

Because no data is returned, the value returned by the execution is not assigned. The only indication that the request has succeeded is that no DataServiceQueryException is raised.

The following example calls the same service operation asynchronously by using CreateQuery:

' Define the service operation query parameter.
Dim city As String = "London"

' Create the DataServiceContext using the service URI.
Dim context As NorthwindEntities = New NorthwindEntities(svcUri2)

' Use the CreateQuery method to create a query that accessess
' the service operation passing a single parameter.       
Dim query = context.CreateQuery(Of Order)("GetOrdersByCity") _
            .AddQueryOption("city", String.Format("'{0}'", city)) _
            .Expand("Order_Details")

' Define the delegate to callback into the process
Dim callback As AsyncCallback = AddressOf OnAsyncQueryExecutionComplete

' Execute the service operation that returns 
' all orders for the specified city.
Dim results = _
    query.BeginExecute(callback, query)
// Define the service operation query parameter.
string city = "London";

// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri2);

// Use the CreateQuery method to create a query that accessess
// the service operation passing a single parameter.       
var query = context.CreateQuery<Order>("GetOrdersByCity")
    .AddQueryOption("city", string.Format("'{0}'", city))
    .Expand("Order_Details");

// Execute the service operation that returns 
// all orders for the specified city.
var results = 
    query.BeginExecute(OnAsyncQueryExecutionComplete, query);
Private Shared Sub OnAsyncQueryExecutionComplete(ByVal result As IAsyncResult)
    ' Get the query back from the stored state.
    Dim query = TryCast(result.AsyncState, DataServiceQuery(Of Order))

    Try
        ' Complete the exection and write out the results.
        For Each o As Order In query.EndExecute(result)

            Console.WriteLine(String.Format("Order ID: {0}", o.OrderID))

            For Each item As Order_Detail In o.Order_Details
                Console.WriteLine(String.Format(vbTab & "Item: {0}, quantity: {1}", _
                    item.ProductID, item.Quantity))
            Next
        Next
    Catch ex As DataServiceQueryException
        Dim response As QueryOperationResponse = ex.Response

        Console.WriteLine(response.Error.Message)
    End Try
End Sub
private static void OnAsyncQueryExecutionComplete(IAsyncResult result)
{
    // Get the query back from the stored state.
    var query = result.AsyncState as DataServiceQuery<Order>;
    
    try
    {
        // Complete the exection and write out the results.
        foreach (Order o in query.EndExecute(result))
        {
            Console.WriteLine(string.Format("Order ID: {0}", o.OrderID));

            foreach (Order_Detail item in o.Order_Details)
            {
                Console.WriteLine(String.Format("\tItem: {0}, quantity: {1}",
                    item.ProductID, item.Quantity));
            }
        }
    }
    catch (DataServiceQueryException ex)
    {
        QueryOperationResponse response = ex.Response;

        Console.WriteLine(response.Error.Message);
    }
}

See Also

Other Resources

WCF Data Services Client Library