In part 1 we looked into some of the plumbing under Ria Services, mostly around the serialization and service routing. We used a “ServiceOperation” as the example in that case. In this part, we’ll look into “Query” type operations under the hood. We won’t rehash overlapping information, so if this information interests you, you might consider reading Part 1 if you have not done so already. The source code for all of these posts can be found on my sky drive location.
Our sample will use this simple DomainService method:
[Query] public IEnumerable<Person> SnagAllPeeps() { return FakeDataAccessLayer.GetAllPeeps(); }
…consumed by our Silverlight client like so:
<data:DataGrid x:Name="grdPeeps"KeyDown="grdPeeps_KeyDown"/>
private void CallQueryOperation(object sender, RoutedEventArgs e) { this.grdPeeps.ItemsSource = _ds.Persons; _ds.Load(_ds.SnagAllPeepsQuery().Where(i=> i.Age>35).OrderBy(i=>i.Name), (LoadOperation<Person> loadOperation) => { // Do whatever here. }, null); }
While the flow under the hood for Query methods is similar to what we discovered when looking at ServiceOperation(s), with some exceptions around passing Linq query information. The generated client-side DomainService (which is referred to as the DomainContext btw), contains a generated method that looks like this:
/// <summary> /// Returns an EntityQuery for query operation 'SnagAllPeeps'. /// </summary> public EntityQuery<Person> SnagAllPeepsQuery() { return base.CreateQuery<Person>("SnagAllPeeps", null, false, true); }
You’ll note that a Systems.Windows.Ria.Data.EntityQuery<Person> is being returned by the base DomainContext.CreateQuery<T> method. An EntityQuery object wraps up information about the query, including the System.Linq.IQueryable query. In this case, we have a predefined query named “SnagAllPeeps”, with no [null] parameters, has No Side effects and IsComposable. HasSideEffects, according to the documentation, will allow a Query operation to use an HTTP Post, while IsComposable determines whether the user can specify Linq operations on the client to apply on the server (hold that thought, you’ll see more about that in a second). Lets see what this Http request looks like:
———————————————————————————————————————————————
GET /ClientBin/DataService.axd/SilverlightApplication1-Web-DomainService1/SnagAllPeeps?$where=(Age%3e35)&$orderby=Name HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET CLR 1.1.4322; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; msn OptimizedIE8;ENUS)
Host: localhost.:53937
Connection: Keep-Alive
———————————————————————————————————————————————
You’ll notice that the EntityQuery where we restricted our age to be greater than 35 and ordering the persons by name was sent in the path of the HTTP Get (“?$where=(Age%3e35)&$orderby=Name“). Pretty slick. Let’s take a closer look. Within the DomainContext.Load method some housekeeping is conducted and the call is handed off to the configured DomainClient, which is our good friend (see Part one) the HttpDomainClient, to the BeginQuery method. The parameters and/or IQueryable query are serialized into a string in the HttpDomainClient.GetOperationData method. If the method does not have side effects then an HTTP GET will be used and the operation data (parameters and serialized IQueryable) are packed onto the HTTP Url(which we can see in the HTTP Get dump above). So, the call is made, and we’re off to the server…..
On the server-side, the same (see Part 1) DataServiceFactory we’ve seen is fired up and a DataService is retrieved and initialized. The parameters are pulled out of the querystring for HTTP GET(s), or the Form collection for HTTP POST(s). Internally a class of type DataServiceQueryRequest is used to invoke the request. That class will orchestrate deserializing QueryParts from the HTTP request (if present), and constructs an IQueryable by rebuilding the Linq expressions on the server side (it appears that Ria Services supports “where”, “orderby”, “skip”, and “take” Linq operators currently). This guy builds up a System.Web.DomainServices.QueryDescription object which packages up a pointer to the method, the [optional] parameters along with the [optional] IQueryable query and hands that off to the DomainService.Query method. Inside this method, the target operation is executed, which in this case is our “SnagAllPeeps” method. If an IQueryable is present, then it is applied to the result. In our sample, the SnagAllPeeps method returns 4 results, but after our client-provided IQueryable passes over the wire (to filter by age), then only 2 results are returned from the server, which you can see below in the Http response:
———————————————————————————————————————————————
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sun, 27 Sep 2009 16:19:25 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/json; charset=utf-8
Content-Length: 430
Connection: Close
{"__type":"DataServiceResult:DomainServices","IsDomainServiceException":false,"Results":[{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web","Id":"951b0038-be14-4338-bb81-f6fa68b315e9","Name":"Bob","Age":41},{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web","Id":"4df1648d-eff8-433f-a2e4-2186ca24ba78","Name":"Joe","Age":38}],"TotalCount":-2,"ResultCount":2}
———————————————————————————————————————————————
You’ll see that in the default DomainService class generated by Visual Studio, there are //TODO tokens indicating the need to add additional queries with parameters. Given what you just learned above, you’d want to refine the original query against a database in the DomainService query, rather than rely on client-side Linq query operators because the latter would retrieve all the rows and filter in the middle tier, whereas the former would allow relational databases to do what they do best (and most efficiently).
Summary
We’ve seen that Query operations are use much of the same plumbing we saw with service operations. Additionally, Query operations bring Linq elements along with serializing IQueryable across the wire, and applying queries against the source retrieved from service operations. In the next post, we’ll dig into the final operation type, the “Submit” operation, along with the C_UD portion of CRUD :) Perhaps others will find this interesting.