Querying the Data Service (WCF Data Services)

The WCF Data Services client library enables you to execute queries against a data service by using familiar .NET Framework programming patterns, including using language integrated query (LINQ). The client library translates a query, which is defined on the client as an instance of the DataServiceQuery<TElement> class, into an HTTP GET request message. The library receives the response message and translates it into instances of client data service classes. These classes are tracked by the DataServiceContext to which the DataServiceQuery<TElement> belongs.

The DataServiceQuery<TElement> generic class represents a query that returns a collection of zero or more entity type instances. A data service query always belongs to an existing data service context. This context maintains the service URI and metadata information that is required to compose and execute the query.

When you use the Add Service Reference dialog to add a data service to a .NET Framework-based client application, an entity container class is created that inherits from the DataServiceContext class. This class includes properties that return typed DataServiceQuery<TElement> instances. There is one property for each entity set that the data service exposes. These properties make it easier to create an instance of a typed DataServiceQuery<TElement>.

A query is executed in the following scenarios:

  • When results are enumerated implicitly, such as:

    • When a property on the DataServiceContext that represents and entity set is enumerated, such as during a foreach (C#) or For Each (Visual Basic) loop.

    • When the query is assigned to a List collection.

  • When the Execute or BeginExecute method is explicitly called.

  • When a LINQ query execution operator, such as First or Single is called.

The following query, when it is executed, returns all Customers entities in the Northwind data service:

// Define a new query for Customers.
DataServiceQuery<Customer> query = context.Customers;

For more information, see How to: Execute Data Service Queries (WCF Data Services).

The WCF Data Services client supports queries for late-bound objects, such as when you use the dynamic type in C#. However, for performance reasons you should always compose strongly-typed queries against the data service. The Tuple type and dynamic objects are not supported by the client.

Because the DataServiceQuery<TElement> class implements the IQueryable<T> interface defined by LINQ, the WCF Data Services client library is able to transform LINQ queries against entity set data into a URI that represents a query expression evaluated against a data service resource. The following example is a LINQ query that is equivalent to the previous DataServiceQuery<TElement> that returns Orders that have a freight cost of more than $30 and orders the results by the freight cost:

var selectedOrders = from o in context.Orders
                     where o.Freight > 30
                     orderby o.ShippedDate descending 
                     select o;

This LINQ query is translated into the following query URI that is executed against the Northwind-based quickstart data service:

http://localhost:12345/Northwind.svc/Orders?Orderby=ShippedDate&?filter=Freight gt 30

The set of queries expressible in the LINQ syntax is broader than those enabled in the representational state transfer (REST)-based URI syntax that is used by data services. A NotSupportedException is raised when the query cannot be mapped to a URI in the target data service.

For more information, see LINQ Considerations (WCF Data Services).

Data service queries support all the query options that WCF Data Servicess provides. You call the AddQueryOption method to append query options to a DataServiceQuery<TElement> instance. AddQueryOption returns a new DataServiceQuery<TElement> instance that is equivalent to the original query but with the new query option set. The following query, when executed, returns Orders that are filtered by the Freight value and ordered by the OrderID, descending:

// Define a query for orders with a Freight value greater than 30
// and that is ordered by the ship date, descending.
DataServiceQuery<Order> selectedOrders = context.Orders
    .AddQueryOption("$filter", "Freight gt 30")
    .AddQueryOption("$orderby", "OrderID desc");

You can use the $orderby query option to both order and filter a query based on a single property, as in the following example that filters and orders the returned Orders objects based on the value of the Freight property:

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

// Define a query for orders with a Freight value greater than 30
// that also orders the result by the Freight value, descending.
DataServiceQuery<Order> selectedOrders = context.Orders
    .AddQueryOption("$orderby", "Freight gt 30 desc");

    // Enumerate over the results of the query.
    foreach (Order order in selectedOrders)
        Console.WriteLine("Order ID: {0} - Freight: {1}",
            order.OrderID, order.Freight);
catch (DataServiceQueryException ex)
    throw new ApplicationException(
        "An error occurred during query execution.", ex);

You can call the AddQueryOption method consecutively to construct complex query expressions. For more information, see How to: Add Query Options to a Data Service Query (WCF Data Services).

Query options give you another way to express the syntactic components of a LINQ query. For more information, see LINQ Considerations (WCF Data Services).

Note Note

The $select query option cannot be added to a query URI by using the AddQueryOption(String, Object) method. We recommend that you use the LINQ Select<TSource, TResult>(IEnumerable<TSource>, Func<TSource, TResult>) method to have the client generate the $select query option in the request URI.

The client executes a query in two parts. Whenever possible, expressions in a query are first evaluated on the client, and then a URI-based query is generated and sent to the data service for evaluation against data in the service. Consider the following LINQ query:

int basePrice = 100;
decimal discount = .10M;

// Define a query that returns products based on a 
// calculation that is determined on the client.
var productsQuery = from p in context.Products
                  where p.UnitPrice >
                  (basePrice - (basePrice * discount)) &&
                  select p;

In this example, the expression (basePrice – (basePrice * discount)) is evaluated on the client. Because of this, the actual query URI http://localhost:12345/northwind.svc/Products()?$filter=(UnitPrice gt 90.00M) and substringof('bike',ProductName) that is sent to the data service contains the already calculated decimal value of 90 in the filter clause. The other parts of the filtering expression, including the substring expression, are evaluated by the data service. Expressions that are evaluated on the client follow common language runtime (CLR) semantics, while expressions sent to the data service rely on the data service implementation of the OData Protocol. You should also be aware of scenarios where this separate evaluation may cause unexpected results, such as when both the client and service perform time-based evaluations in different time zones.

When executed, the DataServiceQuery<TElement> returns an IEnumerable<T> of the requested entity type. This query result can be cast to a QueryOperationResponse<T> object, as in the following example:

// Execute the query for all customers and get the response object.
QueryOperationResponse<Customer> response = 
    query.Execute() as QueryOperationResponse<Customer>;

The entity type instances that represent entities in the data service are created on the client by a process called object materialization. For more information, see Object Materialization (WCF Data Services). The QueryOperationResponse<T> object implements IEnumerable<T> to provide access to the results of the query.

The QueryOperationResponse<T> also has the following members that enable you to access additional information about a query result:

By default, WCF Data Services only returns data that is explicitly selected by the query URI. This gives you the option to explicitly load additional data from the data service when it is needed. A request is sent to the data service each time you explicitly load data from the data service. Data that can be explicitly loaded includes related entities, paged response data, and binary data streams.

Note Note

Because a data service may return a paged response, we recommend that your application use the programming pattern to handle a paged data service response. For more information, see Loading Deferred Content (WCF Data Services).

The amount of data returned by a query can also be reduced by specifying that only certain properties of an entity are returned in the response. For more information, see Query Projections (WCF Data Services).

In some scenarios, it is helpful to know the total number of entities in an entity set and not merely the number returned by the query. Call the IncludeTotalCount method on the DataServiceQuery<TElement> to request that this total count of entities in the set be included with the query result. In this case, the TotalCount property of the returned QueryOperationResponse<T> returns the total number of entities in the set.

You can also get only the total count of entities in the set either as an Int32 or as a Int64 value by calling the Count or LongCount methods respectively. When these methods are called, a QueryOperationResponse<T> is not returned; only the count value is returned. For more information, see How to: Determine the Number of Entities Returned by a Query (WCF Data Services).