Watch recordings of Brian's WCF RIA Services webinars:
This article is Part 2 of the series WCF RIA Services.
Overview
In Part 1, I introduced the basics of WCF RIA Services and walked you through the “hello world” equivalent for WCF RIA Services. That may have all seemed too simple, and you can’t really drag and drop your way to real world applications. For real applications, you are going to need to know more how to customize what services are exposed to the client and how to consume them on the client side. In this article, I’m going to drill down a little deeper into the process of querying data from the client. I’ll cover the conventions for query methods that you define on your domain service, as well as how to do it with configuration (attributes) instead of based on convention. I’ll show some of the programmatic ways that you can perform the queries on the client side. Finally, I’ll talk about using data from sources other than Entity Framework.
The starting point for this article is the solution that resulted from the steps in Part 1, which you can download here.
The finished code for this article can be downloaded here.
Step 1: Add a parameterized collection query to your domain service
In the last article, I walked you through creating a domain service and after you completed the wizard, a single query method had been added to the domain service class that looked like this:
public IQueryable<Task> GetTasks()
{
return this.ObjectContext.Tasks;
}
That query method just returned all of the tasks in the database as an IQueryable<T> and took no arguments. If you want to have more specific queries available to the client without returning all the rows, you can add specific query methods to the domain service, and they will be code generated on the client side as well. You can also choose to return an IEnumerable<T>, but if the underlying provider (i.e. Entity Framework or LINQ to SQL) returns IQueryable<T>, they you should pass that through instead as it supports more scenarios.
Say that a common use case for your application was to allow the user to retrieve tasks for a given date range. They could search for tasks before a given start date or after or between. To support that, add the following query method to your domain service:
public IQueryable<Task> GetTasksByStartDate(
DateTime lowerDateTimeInclusive,
DateTime upperDateTimeInclusive)
{
return this.ObjectContext.Tasks.Where(
t => t.StartDate >= lowerDateTimeInclusive && t.StartDate <= upperDateTimeInclusive);
}
Step 2: Add UI to execute the query
To call the search query instead of the GetTasks query that the DomainDataSource in the UI currently calls at page load, you need to add some UI. Open up MainPage.xaml and click on the DataGrid that is filling the UI. Grab the top edge and drag it down a bit to make room for a few controls. Drag and drop two TextBoxes and one Button so that it looks something like the screen shot below.
Name the first TextBox lowerDate and the second upperDate and the button searchButton. Set the Content of the button to Search By Date. Double click on the button to add a handler.
Step 3: Execute a query on the client through the DomainContext
If you remember from the introduction in Part 1, the DomainContext is the code generated client side counterpart to the server side domain service. In this case, since the domain service is called TasksDomainService, the client counterpart is called TasksDomainContext. It exposes the ability to call the server side asynchronously and to also submit changes made to entities on the client side. The DomainContext is really the brains of the WCF RIA Services on the client side and has a lot of stuff going on internally. Whenever you retrieve entities from the server by executing a query through the domain context, the domain context also holds a reference to those objects along with some change tracking information. So if you modify those objects, it knows which have changed and can send just those entities back to the server side when you decide to submit those changes. But I’ll get into that some more in the next article. For now, lets stay focused on the retrieval side of things.
In your search button click handler, you could add the following code:
private void searchButton_Click(object sender, RoutedEventArgs e)
{
DateTime lowerDateVal;
DateTime upperDateVal;
GetDates(out lowerDateVal, out upperDateVal);
TasksDomainContext context = new TasksDomainContext();
taskDataGrid.ItemsSource = context.Tasks;
EntityQuery<Task> query = context.GetTasksByStartDateQuery(lowerDateVal, upperDateVal);
LoadOperation<Task> loadOp = context.Load(query);
}
The important code here are the last four lines. To call the server side, you need an instance of a domain context. In a real application, you are going to want to create an instance of the domain context that you keep around for a while, because that is where the change tracking information for submitting changes resides. So you will typically move it to a member variable in a view model or possibly an application scoped service. But I’ll get into more detail on that in Part 4 of the series.
The domain context exposes collections of entities that include the sets of collections returned by query methods on your domain service. So far the domain service just exposes a collection of Tasks. Notice that the code is replacing the ItemsSource of the DataGrid with the Tasks collection on this domain context before making any calls against it. This is because when you query through the domain context, the queries execute asynchronously and will replace the contents of the exposed collection when the query returns from the server. That collection implements INotifyCollectionChanged and will raise events that causes the DataGrid (or any data bound control) to refresh itself when those events fire.
The code then gets an EntityQuery<Task> from the context with the arguments that are passed to the corresponding domain service method. That just sets up what you want done, it doesn’t actually make a call. Finally, it gets a LoadOperation<Task> from the context by calling Load. This is the point where a call is actually made to the server, on a background thread, and automatically marshalling the changes onto the UI thread to modify the collection when the results come back.
This is the same thing that is happening under the covers on the DomainDataSource that was used in the XAML in Part 1.
The GetDates method above just extracts the dates from the TextBoxes, checking for empty strings.
Step 4: Add query methods that return a single entity
To return a single entity instead of a collection, you just define a method that does so on your domain service. A corresponding query method will show up on your domain context after compiling.
public Task GetTask(int taskId)
{
return this.ObjectContext.Tasks.FirstOrDefault(t => t.TaskId == taskId);
}
Convention Over Configuration
This is an emerging trend in .NET development– reduce the amount of explicit configuration you need in your code and rely on some naming conventions. It may not be apparent from the domain service code so far, but it has actually been relying on convention. It is not obvious on the query side of things, because any method name will work. But for update, insert, and delete methods, there are a set of named prefixes WCF RIA Services is looking for, such as UpdateTask, InsertTask, and DeleteTask (or a number of variants for each operation).
You can also use “configuration” by decorating the methods with attributes to indicate what kind of operation it is. For the Query side, the attribute is named, not surprisingly, [Query]. Using the attribute can make your code more explicit by making it easy to recognize at a glance what kind of method it is. But the IQueryable<T>, IEnumberable<T> or Entity return type makes it pretty obvious it is a query method anyway. The one advantage of using the Query attribute is that it supports a couple of additional things, such as specifying a maximum number of results (entity count) to return even if the underlying query returns more from the database.
Custom Domain Services
What if you don’t want to use Entity Framework? Another choice is LINQ to SQL. Personally, I would recommend that if you are starting a new project, you focus on Entity Framework. It is really the most capable and likely to be long-lived data access approach for .NET now and in the future. LINQ to SQL is also supported through the separate WCF RIA Services Toolkit if you really want to use that instead.
But a lot of people are using other data access strategies (i.e. nHibernate) and other data sources (Oracle, MySQL, etc.). You can easily use those with WCF RIA Services as well, as long as you can define the data objects you want to pass as simple entities and populate those using whatever code you need to. These are referred to as POCO (Plain Old CLR Objects) domain services.
To do this, you just derive your domain service class from DomainService directly, instead of using the LinqToEntitiesDomainService base class. In the wizard when you Add > New Item > Domain Data Service, just select “empty domain service class” in the Available DataContext/ObjectContext classes drop down. Then define your own entities, return IEnumerable<T> collections of those entities for queries if your underlying data source does not support IQueryable<T>, and do whatever you need to do based on your data source in the methods.
Then you would define your service in terms of your underlying data source and the entities that you are working with. The entity types you work with need to have properties that are other entities or a set of supported types that include all the built in types of the .NET framework.
The key (pun intended) thing that distinguishes your entities is that they need to have a key properties that uniquely identifies them. This is typically an int or Guid. You indicate that property with the [Key] attribute.
The code below shows a simple POCO domain service and its entity type.
public class Foo
{
[Key]
public int FooId { get; set; }
public string Name { get; set; }
}
[EnableClientAccess()]
public class MyPOCODomainService : DomainService
{
public IEnumerable<Foo> GetFoos()
{
return new List<Foo> { new Foo { FooId = 42, Name = "Fred" } };
}
}
In addition to the [Key] attribute, if you have properties on your entity that are entity types themselves (related entities or associations), then you will need to have a property along side that entity that is an ID for that entity that gets set to its key field. Additionally, on the entity property itself, you will need an [Association] attribute to indicate the ID property that sets up the relation and indicate that it is a foreign key. On the entity property, you will also need an [Include] attribute to have that related entity also retrieved when the parent entity is retrieved to the client by RIA Services. See the MSDN Library documentation for these attributes for more information.
Summary
In this article, you got a taste of how queries work with WCF RIA Services. You saw one way to query programmatically and bind to the results. You saw how to define additional query methods and learned about the conventions and configuration options for indicating your query methods in your domain service. You also learned about exposing POCO domain services. In the next article, I’ll show how to make changes to entities on the client side and get those changes made on the server.
The finished code for this article can be downloaded here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net.