Freigeben über


Data Services Expressions – Part 4 – Accessing properties

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

In this part we will talk about accessing properties in the expressions. WCF Data Services may need to access a property value in many different places in the query, but it always uses the same way to do so. Below I’ll use the $filter as an example, but the same would apply to any other place in the query (for example $orderby, $select and so on).

Property metadata

In the WCF Data Services world the shape of the data is defined using metadata classes like ResourceType and ResourceProperty. To get a general idea how to do that, take a look at this series by Alex. Note that even if you’re not using a custom provider, underneath both the built-in providers will define the metadata in the same way, so there’s no hidden magic there.

Currently WCF Data Services uses three kinds of resource types: Entity types, Complex types and Primitive types. Each resource type is defined in the metadata by an instance of ResourceType class.

Both entity and complex types can define properties. A property can be of any resource type (with some limitations, but that’s for another topic). Each property is defined in the metadata by an instance of the ResourceProperty class.

The value of a property is either a primitive type, instance of an entity type, instance of a complex type or null. The actual CLR type of the property value is determined (and defined) by the resource type of the property. Each resource type has an instance type which is the CLR type used to store an instance of that resource type. For example a primitive resource type Edm.String, has instance type System.String. For entity and complex types, the instance type is defined by you in the metadata, usually it’s some class.

Accessing a property value in an expression

We’ll use $filter as a sample of an expression which accesses properties. If you want to know more about the filters please take a look at my previous post.

We’ll use the same sample query here:

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

In the metadata the entity type Product defines a primitive property Rating of type Int32. The filter expression needs to get the value of the Rating property for any given Product and compare it to number 3.

In a filter expression, which is a lambda expression which takes a parameter called “product” which holds an instance of the Product entity type, such property access would look like:

product.Rating

No magic, exactly what you would expect. But …

Typed and untyped properties

In the metadata definition of a property, which is represented by an instance of a ResourceProperty type, there’s a setting called CanReflectOnInstanceProperty. By default this value is set to true, which means that WCF Data Services can use normal CLR way of accessing the property value. In short we’ll call these properties “typed” properties meaning they have a backing CLR type which that property defined.

But if you change this setting to false, it tells WCF Data Services that it must not use the CLR way of accessing such property. For short we’ll call these properties “untyped”, meaning that the CLR type of the resource (entity, complex) doesn’t have such property on it, or it should not be used. Defining a property as untyped gives you full control over how the value of such property is determined for any given resource. But this ability comes with a price, it’s more complicated (more code).

Accessing a typed property

Let’s assume we’re constructing a filter expression as the sample above and thus we have defined a parameter expression which will have the instance of the Product entity we’re filtering:

 ParameterExpression productParameter = 
    Expression.Parameter(typeof(Product), "product");

Accessing a typed property is simple as we’ve shown above. WCF Data Services uses the normal CLR way of accessing the property. In expression trees this means that accessing a property Rating on our product resource is generated like this:

 var rating = Expression.Property(productParameter, "Rating");

Note that this is a shortcut for what actually happens underneath which is:

 var rating = Expression.MakeMemberAccess(
    productParameter, 
    productParameter.Type.GetProperty("Rating"));

The result (in either case) is a MemberExpression where Expression is the productParameter and the Member is the CLR PropertyInfo for the Rating property.

Note that since we’re using reflection to access the property, the CLR type of the property access expression is the same type as the instance type of the Rating CLR property. In our case it’s Itn32.

This mechanism is used to access any “typed” property, be it a primitive, complex or a reference to a resource (or multiple resources).

Accessing an untyped property

If we specify CanReflectOnInstanceProperty = false for our Rating property, the expression generated will be completely different. In this case WCF Data Services must not use CLR reflection to access the property and as a result it doesn’t have a typical way to access the value.

It could use a call to IDataServiceQueryProvider.GetPropertyValue to get the value (as that is the method called to access the property value during serialization for example). But it turns out that it’s not the best way to do so in an expression. The main reason is that such call would require the instance of the IDataServiceQueryProvider to be either specified in the expression as a constant or passed in as a parameter. In either case it complicates the expression and makes it dependant on the actual instance of the IDataServiceQueryProvider. This is not suitable for some providers as they are not always going to use the IDataServiceQueryProvider to access the property value, for example they might translate the entire query into a SQL statement, in which case it doesn’t make sense to have a query provider in there.

So instead WCF Data Services uses special method calls in the expression as basically a placeholder for saying “I want the value of this property here”. Our sample above would then look like:

 (Int32)DataServiceProviderMethods.GetValue(
    product, 
    ratingResourceProperty);

DataServiceProviderMethods is a class which holds all these placeholder methods WCF Data Services uses. The GetValue method is used to specify the intent to get a value of a property on a resource. The ratingResourceProperty is the instance of the ResourceProperty class which was used in the metadata to define the Rating property. It identifies the property to access. And the product is an instance of the resource (in our case the Product entity) for which to get the value of a property.

In expression this will be constructed like this:

 var rating = Expression.Call(
    typeof(DataServiceProviderMethods).GetMethod("GetValue"),
    productParameter,
    Expression.Constant(ratingResourceProperty));

Note that all the methods on the DataServiceProviderMethods class are static, and that their implementation simply throws a NotImplementedException.

This forces the custom provider to replace such expression with some custom way of accessing the property. What this means to us is that it’s more work as we need to replace the expression in the tree with something which will actually get the value. But it also gives us great power. We can do really whatever we want to get the value of the property.

For providers which translate the entire query into a different language (like SQL for example), this provides an easy way to spot the property access in the tree (calls to these special methods are easy to recognize) and it gives them the ResourceProperty instance on which they can store some custom data to help in generating the query.

But even for providers which will execute the LINQ query eventually like a LINQ query (either using LINQ to Objects or something else), this provides the ability to specify a custom way of getting the property value. Using this mechanism we could for example rename a property, change its type, get its value not from a CLR property but from some property bag (Dictionary) or compute it from something completely different.

Untyped property access – the details

There are few more details we need to be aware of though.

First one is that the GetValue method is declared as returning a type System.Object. That alone is not very useful for the expression as it can’t for example be compared to a number. As a result most of the occurrences of GetValue call will be surrounded by a type conversion which casts the object to the actual instance type of the property. So the expression would in fact be constructed by using one more line of code like this:

 rating = Expression.Convert(
    rating, 
    ratingResourceProperty.ResourceType.InstanceType);

The Rating property is of a primitive resource type which has an instance type System.Int32.

The second detail to be aware of is that not all properties are accessed using the GetValue method. Resource set reference properties (properties which represent a one to many relationships) are accessed in a similar fashion through a call to GetSequenceValue. We won’t go into the details of why is it like that, let’s just say that it’s useful since the GetSequenceValue returns IEnumerable<T> which allows for further query composition.

The last detail is that if the given property is an open property, the way to access its value is completely different and we’re not going to describe it here either as it’s a topic for a much larger post.