Querying the Data Service (WCF Data Services)

Important

WCF Data Services has been deprecated and will no longer be available for download from the Microsoft Download Center. WCF Data Services supported earlier versions of the Microsoft OData (V1-V3) protocol only and has not been under active development. OData V1-V3 has been superseded by OData V4, which is an industry standard published by OASIS and ratified by ISO. OData V4 is supported through the OData V4 compliant core libraries available at Microsoft.OData.Core. Support documentation is available at OData.Net, and the OData V4 service libraries are available at Microsoft.AspNetCore.OData.

RESTier is the successor to WCF Data Services. RESTier helps you bootstrap a standardized, queryable, HTTP-based REST interface in minutes. Like WCF Data Services before it, Restier provides simple and straightforward ways to shape queries and intercept submissions before and after they hit the database. And like Web API + OData, you still have the flexibility to add your own custom queries and actions with techniques you're already familiar with.

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.

Data Service Queries

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;
' Define a new query for Customers.
Dim query As DataServiceQuery(Of Customer) = context.Customers

For more information, see How to: Execute Data Service Queries.

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.

LINQ Queries

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;
Dim selectedOrders = From o In context.Orders _
                     Where (o.Freight > 30) _
                     Order By 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

Note

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.

Adding Query Options

Data service queries support all the query options that WCF Data Services 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");
' Define a query for orders with a Freight value greater than 30
' and that is ordered by the ship date, descending.
Dim selectedOrders As DataServiceQuery(Of Order) = 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");

try
{
    // 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);
}
' Create the DataServiceContext using the service URI.
Dim 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.
Dim selectedOrders As DataServiceQuery(Of Order) = _
context.Orders.AddQueryOption("$orderby", "Freight gt 30 desc")

Try
    ' Enumerate over the results of the query.
    For Each order As Order In selectedOrders
        Console.WriteLine("Order ID: {0} - Freight: {1}", _
                order.OrderID, order.Freight)
    Next
Catch ex As DataServiceQueryException
    Throw New ApplicationException( _
            "An error occurred during query execution.", ex)
End Try

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.

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

Note

The $select query option cannot be added to a query URI by using the AddQueryOption method. We recommend that you use the LINQ Select method to have the client generate the $select query option in the request URI.

Client versus Server Execution

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)) &&
                  p.ProductName.Contains("bike")
                  select p;
Dim basePrice As Integer = 100
Dim discount As Decimal = Convert.ToDecimal(0.1)

' Define a query that returns products based on a 
' calculation that is determined on the client.
Dim productsQuery = From p In context.Products
                    Where p.UnitPrice >
                    (basePrice - (basePrice * discount)) AndAlso
                    p.ProductName.Contains("bike")
                    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.

Query Responses

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>;
' Execute the query for all customers and get the response object.
Dim response As QueryOperationResponse(Of Customer) = _
    CType(query.Execute(), QueryOperationResponse(Of 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. 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

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.

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.

Getting a Count of the Total Number of Entities in the Set

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.

In This Section

See also