Freigeben über


Data Services Expressions – Part 3 – Filters

Series: This post is the third part of the Data Service Expressions Series which describes expressions generated by WCF Data Services.

In this part we will look at how filters are represented in the expressions.

The Queryable.Where method

Filters are meant to filter the entities (rows) returned from a given resource set. The client specifies the filter using the $filter query option in the URL. For example (using the sample service from odata.org):

https://services.odata.org/OData/OData.svc/Products?$filter=Rating gt 3

The intention of this query is to evaluate the filter expression (the part after the equal sign) for each product in the Products resource set, convert the result to a boolean and only return those products which return true. So the above query should return only products with rating greater than 3.

WCF Data Services takes this query and translates it into an expression tree. In expression trees, the way to express filters is to use the Where method. So the query would look like this:

 products.Where(product => product.Rating > 3);

This is a nice C# way of writing what in fact looks like this:

 Queryable.Where(products, product => product.Rating > 3);

The Queryable.Where is an extension method for IQueryable<T>, so that’s why it can be called on the products as if it was its member method. The other thing is that it’s a generic method which takes the T (the type of one resource) as a generic parameter. So it actually looks like this:

 Queryable.Where<Product>(products, product => product.Rating > 3);

The first parameter is the IQueryable<T> which returns the resources to filter. The second parameter is the filter expression. It is an instance of

 Expression<Func<Product, bool>>

So it’s an expression which represents a function which takes a single parameter of type Product and returns a boolean. In expression trees functions like this are expressed using lambda expressions.

Lambda expressions

A lambda expression takes a list of parameters (the parameters of the function) and a lambda body. Just like typical function takes parameters and has some body which computes the result.

Parameters are simply an array of instances of ParameterExpression expression node. Each such instance represents a parameter (you give it a type and a name).

The lambda body is an expression tree which can reference these parameters whenever it needs to access the value of the given parameter.

So the above filter expression is just a nice C# way of writing:

 ParameterExpression productParameter = Expression.Parameter(typeof(Product), "product");
Expression body = Expression.GreaterThan(
    Expression.Property(productParameter, "Rating"),
    Expression.Constant(3));
var lambda = (Expression<Func<Product, bool>>)Expression.Lambda(
    body,
    productParameter);

For now let’s ignore how exactly the body is constructed and its parts, we’ll talk about these in some later post, instead let’s focus on how the lambda expression itself is used.

Filter expressions

And now back to our filter, using the way to view expressions generated by WCF Data Services from the previous blog post, let’s take a look at expression generated for our sample query:

https://host/service.svc/Products?$filter=Rating gt 3

The expression looks like this (result of the Expression.ToString() method):

System.Collections.Generic.List`1[TypedService.Product].Where(it => (it.Rating > 3))

Note that this is using the nice C# notation in which the Where call is treated as instance method and it shows the filter expression as the C# lambda expression. WCF Data Services calls the parameter expression for the filter expression which holds the resource instance “it”. It’s the same thing as in our samples above where we were using “product”.

To see the real expression, let’s look at the Expression.DebugView:

.Call System.Linq.Queryable.Where(

    .Constant<System.Linq.EnumerableQuery`1[TypedService.Product]>(System.Collections.Generic.List`1[TypedService.Product]),

    '(.Lambda #Lambda1<System.Func`2[TypedService.Product,System.Boolean]>))

.Lambda #Lambda1<System.Func`2[TypedService.Product,System.Boolean]>(TypedService.Product $it) {

    $it.Rating > 3

}

Here we can see that the expression is actually a Call to the Queryable.Where method, which takes two parameters. The first parameter is the query root (see the previous blog post for discussion about the query root expressions). The second parameter is the lambda expression called #Lambda1 which represents the filter expression. As you can see above that lambda expression takes one parameter of type Product called $it and its body is $it.Rating > 3.

This was just the basic structure of how filters are handled by WCF Data Services. In the next post we’ll talk about the filter expression itself and specifically about the part of access property values.