How does WAQS querying work?

7 reasons to use WAQS

WAQS documentation

Inflexible services issue

The issue when you use a classic WCF service and when you have many queries is the fact that it’s often a pain due to the flexibility missing.

For example, with Northwind,

If you want to get all customers, we will have a GetCustomers method on the service.

If you want to get the customers living in Paris, we will add a GetCustomersByCity method.

If you want to get the customers, per their age, we will add a GetCustomersByAge method.

If you want to get the customers, living in Paris per their age and using pagination, we will again add a new method.

You can try using one method with many arguments but it implies many many many parameters or many limitations.

So it will quickly become a nightmare!

Moreover, coding a new application or some new features will probably require to modify the service which prevent some scenarios as having a client coded by a third party.

Ideally, for Data Centric applications, we want to be able to querying directly from the client.

To do it, Microsoft proposes a technology: WCF Data Services based on OData protocol.

If OData is a very interesting solution, it has a big issue that could be crippling in many projects: querying limitation.

Indeed, with OData you can only use basics WHERE, ORDER BY, SELECT and pagination.

For example, it is not possible to get the total spent by each customer using OData.

In order to querying, WAQS proposes a really more powerful LINQ provider with a feature perimeter even better than LINQ To Entities one.

In this post (and many future ones), we will work with the following model.

image

To get the total spent by customer, you can use the following query with WAQS:

var totalSpentPerCustomer = await (from c in _context.Customers.AsAsyncQueryable() 
                                                          let totalSpent = (double?)c.Orders.Sum(o => 
                                                              o.OrderDetails.Sum(od => od.UnitPrice * od.Quantity * (1 – od.Discount)))
                                                          select new CustomerWithTotalSpent { Customer = c, TotalSpent = totalSpent ?? 0 })
                                                         .ExecuteAsync();

 

How does WAQS work?

First, contrary to classic IQueryable approach, WAQS considers that the fact to call the server (and then the database) should not be transparent.

With WAQS, if you use a LINQ query on _context.Customers, you are executing a LINQ To Object query on customers already loaded in the context cache. To call the server, you have to use the AsAsyncQueryable method.

Moreover, it’s important to use some asynchronous server queries in order to not freeze the UI.

IQueryable interface, classically used for querying does not allow asynchronous execution. WAQS generates two new interfaces: IAsyncQueryable<T> (to get a collection) and IAsyncQueryableValue<T> (to get only one instance, after a First or a Count for example).

AsAsyncQueryable method returns an instance of IAsyncQueryable<T>.

Every LINQ extension methods are generated by WAQS for IAsyncQueryable<T>.

Query serialization

In order to be able to execute the query on the server side, we have to send it the query. It’s done by Execute method.

The issue in this case is the fact that LINQ expressions are not serializable.

So, WAQS defines its own SerializableExpression in order to serialize Expression information to be able to build the LINQ Expression on the server side and then been able to execute this query using Entity Framework.

Then, we have a new issue. To build the LINQ expression on the server type, we have to know every query used types. But the anonymous type used in the previous query is not known by the server.

To fix this issue, the SerializableExpression includes used types description when these types are not defined in mscorlib.dll with a namespace starting with System and when the type does not have the DataContract attribute.

This description includes the name and the type of each of its read/write properties.

On the server side, when the type is unknown, WAQS uses the type description to generate the type at runtime (using IL emit).

Then this type is put in a cache in the server in order to not have to create it anytime.

Now that this issue is fixed, there is a new one: the result serialization.

The queries result type is a NorthwindQueryResult (in our sample).

image

If the type is known, WAQS uses Value or Values properties function of the return type (one instance or a collection).

If the type is not known, WAQS serializes every property one per one applying the same logic.

To optimize performances, serialization procedure makes an ExpressionTree, at runtime, which will be compiled in order to have a delegate that can be pushed into a cache in the server.

Like this, next time we will execute a query returning the same type, the serialization process will use the delegate, instead of using serialization, like if we wrote the code on the server to serialize the type.

On the client side, if the type is known on the server, WAQS uses Value or Values properties to return the query result. Else, using Reflection, WAQS builds the result using QueryResultRecords.

Note that, on the client side, we could build an expression in order to put into the cache the instantiation process (as it does on the server). However, it is less important than in the server. And, in addition, there is a better way using DTO.

 

DTO Usage

Using a server unknown type as query result implies a more important instantiation cost.

Indeed, even with optimizations previously described, the serialization property by property could be significant.

So, to optimize the process, the best is to define the type on the server.

As wrote in introduction, one of the WAQS idea is to pick out the technical code. So this class don’t need DataContract / DataMember attributes.

By default, after NuGet code generation, WAQS creates a DTO folder in the server. In this folder, we will define the following class:

public class CustomerWithTotalSpent
{
    public Customer Customer { get; set; }
    public double TotalSpent { get; set; }
}


Then we just have to compile and regenerate T4 templates to have this class in the client side.









Then, we could write our query using this type :









var totalSpentPerCustomer = await (from c in _context.Customers.AsAsyncQueryable()
                                 let totalSpent = (double?)c.Orders.Sum(o =>
                                     o.OrderDetails.Sum(od => od.UnitPrice * od.Quantity * (1 - od.Discount)))
                                   select new CustomerWithTotalSpent { Customer = c, TotalSpent = totalSpent ?? 0 })
                                  .ExecuteAsync();


How does serialization work even though we didn’t use DataContract and DataMember attributes?



In fact, the CustomerWithTotalSpent class we wrote won’t be used by WAQS code. It’s just a model for code generation to generate a new class in the server and another one in the client with DataContract / DataMember attributes:







[System.Runtime.Serialization.DataContractAttribute(IsReference = true, Namespace = http://Northwind/DTO)]
public partial class CustomerWithTotalSpent
{
    [System.Runtime.Serialization.DataMemberAttribute]
    public Customer Customer
    {
        get;
        set;
    }

    [System.Runtime.Serialization.DataMemberAttribute]
    public double TotalSpent
    {
        get;
        set;
    }
}


With this way, it’s possible to serialize in only once a CustomerWithTotalSpent list.

This entry was posted in 12253, 16868. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>