Using Telerik’s new LINQ implementation with WCF RIA Services Part II: Query Methods

In my last
blog post, I showed how Telerik’s new LINQ
implementation works with WCF
RIA Services. In that post I built a Domain Model from the Northwind database
as well as a RIA Services Domain Service. I then showed the drag and drop features
of RIA Services and created a simple Silverlight application with no code. Today we
are going to take that example one step further by creating some custom server side
Query Methods.

A query method is just a facility to query a data source. In RIA Services, you define
a query method explicitly so it can be used on the client. This is pretty straight
forward with RIA Services. Let’s create a query method to query the Customer table
by its primary key (CustomerID) in the database. To do this, open the project we used
in the previous
blog post and add this code to the DomainService class in the server project.

1: //This query method
will return only 1 customer

2: [Query(IsComposable = false)]

3: public Customer
GetCustomersByID(string customerID)

4: {

5: //must also include
the Germany restriction

6: //to keep in sync with the GetCustomers
business logic

7: returnthis.DataContext.Customers.SingleOrDefault

8: (c => c.CustomerID == customerID

9: && c.Country=="Germany");

10: }

This method will return one customer and you need to specify that by the attribute
IsComposable=False (Line 2). Everything else is pretty basic, you have a method signature
that accepts a parameter (Line 3) and a LINQ statement that filters the data by CustomerID
as well as by country (lines 8-9). We are filtering by country as well because in
our original business logic (in Part I) we had a GetCustomers() method that filtered
all of the records by the country Germany. This new GetCustomersByID method knows
nothing of the GetCustomers() method so we have to replicate that business logic here.
(We have hard coded the value of Germany, in a production application, you would most
likely obtain this value from a database or cookie after authentication.)

Let’s create a second query method, one that will filter the Customer data source
by the ContactName field and return a collection, not a single item. We define an
IQueryable collection of Customer as the return value in the method signature (Line
3) and accept a parameter. This parameter is used in our LINQ statement to filter
the data source (Lines 9-10). In addition, just like the previous example, we must
also filter by the country Germany; also replicate the OrderBy of our GetCustomers()
method (Line 11).

1: //This query method
will return a collection of customers

2: //filtered by the letters passed
in on the contact name

3: public IQueryable<Customer>
GetCustomersByLetter(string letter)

4: {

5: //must also include
the Germany restriction

6: //to keep in sync with the GetCustomers
business logic

7: //also since we
are returning a collection, must

8: //respect the OrderBy as well
from the business logic

9: returnthis.DataContext.Customers.Where

10: (c => c.ContactName.StartsWith(letter) == true

11: && c.Country == "Germany").OrderBy(c
=> c.CustomerID);

12: }

Now that we have defined two query methods, let’s wire them up to our XAML form in
the Silverlight application.

In our Silverlight application, delete the grid that we had dragged onto the form
in Part I. Replace it with two labels, two text boxes, two buttons and a grid (set
the grid’s AutoGenerateColumns property to True.) Your XAML page should look something
like this:

Now we have to write some code.

In the last blog post we were able to use the drag and drop features of RIA Services
and not write any code. Today I will show you how to perform similar and more advanced
functions with just a little bit of code. First we need two using statements in order
to get working:

using SilverlightApplication6.Web;
using System.ServiceModel.DomainServices.Client;

Next we need to create a global variable for the RIA Services DomainService’s context.

1: //domain context
for all RIA operations

2: private DomainService1
domainContext = new DomainService1();

Next we will load the grid with all of the data the first time the XAML form loads.
We load the data by calling the GetCustomers() method we created in the previous blog
post (we use the domainContext global variable in line 6.).

1: void MainPage_Loaded(object sender,
RoutedEventArgs e)

2: {

3: //since we are going
across the wire, must explicitly tell

4: //RIA Services that we are going
to load data

5: LoadOperation<Customer> loadOperation
=

6: domainContext.Load<Customer>(domainContext.GetCustomersQuery());

7: //the actual binding
of the results, RIA takes care of the async

8: this.dataGrid1.ItemsSource
= loadOperation.Entities;

9: }

This code does the same thing as the drag and drop did in the previous blog post,
call GetCustomers() (Lines 5-6) and bind the results (line 8). Notice in the codegen
on the client, RIA Services appends the word “Query” to all query methods. In
the previous blog post this was done automatically, but today we did it via code.
If we run this it will give us the following view:

Now let’s wire up the buttons so we can perform the filters. First we will wire up
the button that will search by CustomerID. That button click event will call the GetCustomerByID
query method (lines 11-13) and bind the results (line 15.) We have to pass in the
data the user entered in the text box, make sure in production to validate this data!

1: privatevoid button1_Click(object sender,
RoutedEventArgs e)

2: {

3: //disable the buttons
during the async load

4: //to prevent the user from clicking
twice while waiting

5: button1.IsEnabled = false;

6: button2.IsEnabled = false;

7:

8: //since we are going across
the wire, must explicitly tell

9: //RIA Services that
we are going to load data

10: //Also here is where you pass
the parameter in

11: LoadOperation<Customer> loadOp
= domainContext.Load

12: (domainContext.GetCustomersByIDQuery(textBox1.Text),

13: CustomerLoadedCallback, null);

14: //the actual data binding,
RIA takes care of the async

15: dataGrid1.ItemsSource = loadOp.Entities;

16: }

As part of the operation, RIA Services will handle the asynchronous processing for
you. The problem is that users are not used to async operations, so they may try to
click on the button more than once. We account for this by disabling the buttons (lines
5-6) until the operation is complete. We have to catch the end of the async
operation in a callback function and pass that in as a parameter to the operation
(line 13). The callback function is here:

Let’s run this and test it out. If you filter by “ALFKI”, the results look like this:

Now let’s do the same for the the filter by ContactName. The code behind the button
event is here:

1: privatevoid button2_Click(object sender,
RoutedEventArgs e)

2: {

3: //disable the buttons
during the async load

4: //to prevent the user from clicking
twice while waiting

5: button1.IsEnabled = false;

6: button2.IsEnabled = false;

7:

8: //since we are going across
the wire, must explicitly tell

9: //RIA Services that
we are going to load data

10: //Also here is where you pass
the parameter in

11: LoadOperation<Customer> loadOp
= domainContext.Load

12: (domainContext.GetCustomersByLetterQuery(textBox2.Text),

13: CustomerLoadedCallback, null);

14: //the actual data binding,
RIA takes care of the async

15: dataGrid1.ItemsSource = loadOp.Entities;

16: }

Similar to the previous example, we are calling the query method, this time GetCustomersByLetter
(lines 11-13) and passing in the value the user typed into the text box. When we run
this and filter by all contacts that start with the letter H, it looks like this:

Hopefully with these two examples you can see the power of using Telerik’s new LINQ
implementation and WCF RIA Services.

Steve Forte

Stephen Forte sits on the board of several start-ups including Triton Works. Stephen is also the Microsoft Regional Director for the NY Metro region and speaks regularly at industry conferences around the world. He has written several books on application and database development including Programming SQL Server 2008 (MS Press).

Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.