LINQ Considerations (WCF Data Services)

This topic provides information about the way in which LINQ queries are composed and executed when you are using the WCF Data Services client and limitations of using LINQ to query a data service that implements the Open Data Protocol (OData). For more information about composing and executing queries against an OData-based data service, see Querying the Data Service.

Composing LINQ Queries

LINQ enables you to compose queries against a collection of objects that implements IEnumerable<T>. Both the Add Service Reference dialog box in Visual Studio and the DataSvcUtil.exe tool are used to generate a representation of an OData service as an entity container class that inherits from DataServiceContext, as well as objects that represent the entities returned in feeds. These tools also generate properties on the entity container class for the collections that are exposed as feeds by the service. Each of these properties of the class that encapsulates the data service return a DataServiceQuery<TElement>. Because the DataServiceQuery<TElement> class implements the IQueryable<T> interface defined by LINQ, you can compose a LINQ query against feeds exposed by the data service, which are translated by the client library into a query request URI that is sent to the data service on execution.

Important

The set of queries expressible in the LINQ syntax is broader than those enabled in the URI syntax that is used by OData data services. A NotSupportedException is raised when the query cannot be mapped to a URI in the target data service. For more information, see the Unsupported LINQ Methods in this topic.

The following example is a LINQ query that returns Orders that have a freight cost of more than $30 and sorts the results by the shipping date, starting with the latest ship date:

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  

For more general information about LINQ, see LINQ (Language-Integrated Query).

LINQ enables you to compose queries by using both the language-specific declarative query syntax, shown in the previous example, as well as a set of query methods known as standard query operators. An equivalent query to the previous example can be composed by using only the method-based syntax, as shown the following example:

var selectedOrders = context.Orders
                    .Where(o => o.Freight > 30)
                    .OrderByDescending(o => o.ShippedDate);
Dim selectedOrders = context.Orders _
                     .Where(Function(o) o.Freight.Value > 30) _
                     .OrderByDescending(Function(o) o.ShippedDate)

The WCF Data Services client is able to translate both kinds of composed queries into a query URI, and you can extend a LINQ query by appending query methods to a query expression. When you compose LINQ queries by appending method syntax to a query expression or a DataServiceQuery<TElement>, the operations are added to the query URI in the order in which methods are called. This is equivalent to calling the AddQueryOption method to add each query option to the query URI.

Executing LINQ Queries

Certain LINQ query methods, such as First<TSource>(IEnumerable<TSource>) or Single<TSource>(IEnumerable<TSource>), when appended to the query, cause the query to be executed. A query is also executed when results are enumerated implicitly, such as during a foreach loop or when the query is assigned to a List collection. For more information, see Querying the Data Service.

The client executes a LINQ query in two parts. Whenever possible, LINQ 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. For more information, see the section Client versus Server Execution in Querying the Data Service.

When a LINQ query cannot be translated in an OData-compliant query URI, an exception is raised when execution is attempted. For more information, see Querying the Data Service.

LINQ Query Examples

The examples in the following sections demonstrate the kinds of LINQ queries that can be executed against an OData service.

Filtering

The LINQ query examples in this section filter data in the feed returned by the service.

The following examples are equivalent queries that filter the returned Orders entities so that only orders with a freight cost greater than $30 are returned:

  • Using LINQ query syntax:
var filteredOrders = from o in context.Orders
                        where o.Freight > 30
                        select o;
Dim filteredOrders = From o In context.Orders
                        Where o.Freight.Value > 30
                        Select o
  • Using LINQ query methods:
var filteredOrders = context.Orders
    .Where(o => o.Freight > 30);
Dim filteredOrders = context.Orders.Where(Function(o) o.Freight.Value > 0)
  • The URI query string $filter option:
// Define a query for orders with a Freight value greater than 30.
var filteredOrders
    = context.Orders.AddQueryOption("$filter", "Freight gt 30M");
' Define a query for orders with a Freight value greater than 30.
Dim filteredOrders _
            = context.Orders.AddQueryOption("$filter", "Freight gt 30M")

All of the previous examples are translated to the query URI: http://localhost:12345/northwind.svc/Orders()?$filter=Freight gt 30M.

Sorting

The following examples show equivalent queries that sort returned data both by the company name and by postal code, descending:

  • Using LINQ query syntax:
var sortedCustomers = from c in context.Customers
                     orderby c.CompanyName ascending, 
                     c.PostalCode descending
                     select c;
Dim sortedCustomers = From c In context.Customers
                             Order By c.CompanyName Ascending,
                             c.PostalCode Descending
                             Select c
  • Using LINQ query methods:
var sortedCustomers = context.Customers.OrderBy(c => c.CompanyName)
    .ThenByDescending(c => c.PostalCode);
Dim sortedCustomers = context.Customers.OrderBy(Function(c) c.CompanyName) _
.ThenByDescending(Function(c) c.PostalCode)
  • URI query string $orderby option):
var sortedCustomers = context.Customers
    .AddQueryOption("$orderby", "CompanyName, PostalCode desc");
Dim sortedCustomers = context.Customers _
                      .AddQueryOption("$orderby", "CompanyName, PostalCode desc")

All of the previous examples are translated to the query URI: http://localhost:12345/northwind.svc/Customers()?$orderby=CompanyName,PostalCode desc.

Projection

The following examples show equivalent queries that project returned data into the narrower CustomerAddress type:

  • Using LINQ query syntax:
var projectedQuery = from c in context.Customers
            select new CustomerAddress
            {
                CustomerID = c.CustomerID,
                Address = c.Address,
                City = c.City,
                Region = c.Region,
                PostalCode = c.PostalCode,
                Country = c.Country
            };
Dim projectedQuery = From c In context.Customers
                     Select New CustomerAddress With
                    {
                        .CustomerID = c.CustomerID,
                        .Address = c.Address,
                        .City = c.City,
                        .Region = c.Region,
                        .PostalCode = c.PostalCode,
                        .Country = c.Country
                    }
  • Using LINQ query methods:
var projectedQuery = context.Customers.Where(c => c.Country == "Germany")
    .Select(c => new CustomerAddress
    {
        CustomerID = c.CustomerID, 
        Address = c.Address,
        City = c.City,
        Region = c.Region,
        PostalCode = c.PostalCode,
        Country = c.Country});                   
Dim projectedQuery = context.Customers.Where(Function(c) c.Country = "Germany") _
            .Select(Function(c) New CustomerAddress With
            {
                .CustomerID = c.CustomerID,
                .Address = c.Address,
                .City = c.City,
                .Region = c.Region,
                .PostalCode = c.PostalCode,
                .Country = c.Country
            })
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.

Both of the previous examples are translated to the query URI: "http://localhost:12345/northwind.svc/Customers()?$filter=Country eq 'GerGerm'&$select=CustomerID,Address,City,Region,PostalCode,Country".

Client Paging

The following examples show equivalent queries that request a page of sorted order entities that includes 25 orders, skipping the first 50 orders:

  • Applying query methods to a LINQ query:
var pagedOrders = (from o in context.Orders
                      orderby o.OrderDate descending
                     select o).Skip(50).Take(25);
Dim pagedOrders = (From o In context.Orders
                   Order By o.OrderDate Descending
                   Select o) _
               .Skip(50).Take(25)
  • URI query string $skip and $top options):
var pagedOrders = context.Orders
    .AddQueryOption("$orderby", "OrderDate desc")
    .AddQueryOption("$skip", 50)
    .AddQueryOption("$top", 25);
Dim pagedOrders = context.Orders _
                  .AddQueryOption("$orderby", "OrderDate desc") _
                  .AddQueryOption("$skip", 50) _
                  .AddQueryOption("$top", 25) _

Both of the previous examples are translated to the query URI: http://localhost:12345/northwind.svc/Orders()?$orderby=OrderDate desc&$skip=50&$top=25.

Expand

When you query an OData data service, you can request that entities related to the entity targeted by the query be included the returned feed. The Expand method is called on the DataServiceQuery<TElement> for the entity set targeted by the LINQ query, with the related entity set name supplied as the path parameter. For more information, see Loading Deferred Content.

The following examples show equivalent ways to use the Expand method in a query:

  • In LINQ query syntax:
var ordersQuery = from o in context.Orders.Expand("Order_Details")
                     where o.CustomerID == "ALFKI"
                     select o;
Dim ordersQuery = From o In context.Orders.Expand("Order_Details")
                     Where o.CustomerID = "ALFKI"
                     Select o
  • With LINQ query methods:
var ordersQuery = context.Orders.Expand("Order_Details")
                  .Where(o => o.CustomerID == "ALFKI");
Dim ordersQuery = context.Orders.Expand("Order_Details") _
                          .Where(Function(o) o.CustomerID = "ALFKI")

Both of the previous examples are translated to the query URI: http://localhost:12345/northwind.svc/Orders()?$filter=CustomerID eq 'ALFKI'&$expand=Order_Details.

Unsupported LINQ Methods

The following table contains the classes of LINQ methods are not supported and cannot be included in a query executed against an OData service:

Operation Type Unsupported Method
Set operators All set operators are unsupported against a DataServiceQuery<TElement>, which included the following:

- All
- Any
- Concat
- DefaultIfEmpty
- Distinct
- Except
- Intersect
- Union
- Zip
Ordering operators The following ordering operators that require IComparer<T> are unsupported against a DataServiceQuery<TElement>:

- OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>)
- OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>)
- ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>)
- ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>)
Projection and filtering operators The following projection and filtering operators that accept a positional argument are unsupported against a DataServiceQuery<TElement>:

- Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>, IEqualityComparer<TKey>)
- Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,Int32,TResult>)
- SelectMany<TSource,TResult>(IEnumerable<TSource>, Func<TSource,IEnumerable<TResult>>)
- SelectMany<TSource,TResult>(IEnumerable<TSource>, Func<TSource,Int32,IEnumerable<TResult>>)
- SelectMany<TSource,TCollection,TResult>(IEnumerable<TSource>, Func<TSource,IEnumerable<TCollection>>, Func<TSource,TCollection,TResult>)
- SelectMany<TSource,TCollection,TResult>(IEnumerable<TSource>, Func<TSource,Int32,IEnumerable<TCollection>>, Func<TSource,TCollection,TResult>)
- Where<TSource>(IEnumerable<TSource>, Func<TSource,Int32,Boolean>)
Grouping operators All grouping operators are unsupported against a DataServiceQuery<TElement>, including the following:

- GroupBy
- GroupJoin

Grouping operations must be performed on the client.
Aggregate operators All aggregate operations are unsupported against a DataServiceQuery<TElement>, including the following:

- Aggregate
- Average
- Count
- LongCount
- Max
- Min
- Sum

Aggregate operations must either be performed on the client or be encapsulated by a service operation.
Paging operators The following paging operators are not supported against a DataServiceQuery<TElement>:

- ElementAt
- Last
- LastOrDefault
- SkipWhile
- TakeWhile Note: Paging operators that are executed on an empty sequence return null.
Other operators The following other operators are not supported against a DataServiceQuery<TElement>:

1. Empty
2. Range
3. Repeat
4. ToDictionary
5. ToLookup

Supported Expression Functions

The following common-language runtime (CLR) methods and properties are supported because they can be translated in a query expression for inclusion in the request URI to an OData service:

String Member Supported OData Function
Concat(String, String) string concat(string p0, string p1)
Contains(String) bool substringof(string p0, string p1)
EndsWith(String) bool endswith(string p0, string p1)
IndexOf(String, Int32) int indexof(string p0, string p1)
Length int length(string p0)
Replace(String, String) string replace(string p0, string find, string replace)
Substring(Int32) string substring(string p0, int pos)
Substring(Int32, Int32) string substring(string p0, int pos, int length)
ToLower() string tolower(string p0)
ToUpper() string toupper(string p0)
Trim() string trim(string p0)
DateTime Member1 Supported OData Function
Day int day(DateTime p0)
Hour int hour(DateTime p0)
Minute int minute(DateTime p0)
Month int month(DateTime p0)
Second int second(DateTime p0)
Year int year(DateTime p0)

1The equivalent date and time properties of Microsoft.VisualBasic.DateAndTime, as well as the DatePart method in Visual Basic are also supported.

Math Member Supported OData Function
Ceiling(Decimal) decimal ceiling(decimal p0)
Ceiling(Double) double ceiling(double p0)
Floor(Decimal) decimal floor(decimal p0)
Floor(Double) double floor(double p0)
Round(Decimal) decimal round(decimal p0)
Round(Double) double round(double p0)
Expression Member Supported OData Function
TypeIs(Expression, Type) bool isof(type p0)

The client may also be able to evaluate additional CLR functions on the client. A NotSupportedException is raised for any expression that cannot be evaluated on the client and cannot be translated into a valid request URI for evaluation on the server.

See Also

Querying the Data Service
Query Projections
Object Materialization
OData: URI Conventions