Skip to main content Skip to footer

RIA Services, LINQ to Entities and C1DataGrid

Creating your app the best, cleanest way from the start is everyone's main objective when starting out or starting over. Everyone strives for that "n-tier" architecture in their application design, and by that I mean having each tier of the application in its own separate and distinct layer (presentation, data, business, etc). This architecture leads to cleaner designs and scalability because developers only have to modify or add specific layers when it comes time to change the UI screens or the underlying technology, etc. A full-blown rewrite is no longer necessary. This is where Silverlight and WCF RIA Services fit in. WCF RIA Services provides a pattern to write application logic that runs on the mid-tier and controls access to data for queries, changes and custom operations. The RIA Services framework enables developers to write logic that runs on the server and can be easily accessible on the client, such as inside Silverlight. WCF RIA Services are the future of business application development in Silverlight and it's never too late to jump on board and learn how to take advantage of these great technologies. This blog post walks through creating a Silverlight project using WCF RIA Services, the Entity Framework, LINQ to Entities and the ComponentOne DataGrid control. Not only will we have binding for one datagrid, but we will also show how to create a hierarchical scenario where you have multiple related tables showing data from these services. Requirements for using RIA Services:

  • Visual Studio 2010 and .NET 4.0
  • Silverlight 4 Tools
  • WCF RIA Services for Silverlight 4 (more info)

Additional requirements for creating this sample:

  • ComponentOne DataGrid control (more info)
  • Northwind SQL database

Creating the Project

To get started, create a new Silverlight project in Visual Studio 2010. Select the default Silverlight Application template. To follow along with the sample steps and code below, select C# and rename the project to DataGridRIAServices. Select the 4.0 Framework and in the new Silverlight Application dialog box, check the Enable WCF RIA Services check box. This is important as it adds the necessary DomainServices references to our project.

Adding the Entity Data Model

We will use the Entity Framework to generate our object model for us. We basically just point it to our data and press _go_. Add an ADO.NET Entity DataModel, named Northwind.edmx, to your .Web Project. In the Entity Data Model Wizard, select "Generate from database" and then choose your data connection. For this sample I used a simple Northwind database from SQLExpress. Select the Employee, EmployeeTerritories, Order and Order Details tables as the Data Objects. Then click Finish. This will generate the full object model for these selected data tables. You can view the data model displayed in the ADO.NET Entity Data Model Designer (Northwind.edmx). Next, we will have Visual Studio auto-generate methods to retrieve this data because we want to write as little of this repetitive type of code as possible!

Using LINQ to Entities

LINQ to Entities is, specifically, a part of the ADO.NET Entity Framework which allows LINQ query capabilities. Basically, it enables you to write LINQ queries against the Entity Framework. To work with LINQ to Entities, we first add a Domain Service Class to our project, named NorthwindDomainService.cs. When you use the Add New Domain Service Class dialog box to create a domain service that exposes LINQ to Entities types, the dialog box automatically creates a class that derives from LinqToEntitiesDomainService(Of TContext). The class is created with methods that provide a starting point for implementing business logic in your application. A query method is included in the class. If you select the Enable Editing check box, insert, update, and delete methods are included. You should add methods or customize the existing methods to meet the requirement of your application. For this sample we are just going to retrieve data, so the default classes are fine. Click OK. Now we have a place to put all our application logic generated for us. Open the NorthwindDomainClass.cs and notice the class structure generated. The purple squiggly lines are just from my IntelliSpell turned on, but this is where we add our own logic and additional properties. For instance, let's add a GetEmployees_Orders() item to return Employees and Orders. Here we use the LINQ Include statement to add the related Orders table to our Employees query. Queries against the Entity Framework are represented by command tree queries, which execute against the object context. Add this code directly after the GetEmployees method.


public IQueryable<Employee> GetEmployees_Orders()  
{  
    return this.ObjectContext.Employees.Include("Orders");  
}  

That's all the little bit of code we need to write for this sample (on the data side of things). Now let's move to the presentation layer - our Silverlight page.

Working with C1DataGrid and RIA Services

You can easily bind the C1DataGrid control to your RIA Services data source in just a few steps. For this sample we will also set up a hierarchical datagrid. 1) To begin working with C1DataGrid and RIA Services, add references to the following assemblies from ComponentOne to your Silverlight project:

  • C1.Silverlight
  • C1.Silverlight.DataGrid
  • C1.Silverlight.DataGrid.RIA
  • System.Windows.Controls.DomainServices*

*System.Windows.Controls.DomainServices.dll is typically found under Program Files\Microsoft SDKs\RIA Services\Libraries\Silverlight 2) Add the corresponding language namespace declarations to the top of MainPage.xaml, as well as a "local" declaration pointing to the Web project. This will be used to access our Domain Service.


xmlns:c1dg="clr-namespace:C1.Silverlight.DataGrid;assembly=C1.Silverlight.DataGrid"  
xmlns:c1adapter="clr-namespace:C1.Silverlight.DataGrid.Ria;assembly=C1.Silverlight.DataGrid.Ria"  
xmlns:ria="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"  
xmlns:local="clr-namespace:DataGridRiaServices.Web"  

3) Insert the following XAML between the tags in MainPage.xaml. This defines the parent C1DataGrid control, its first child, as well the RIA domain data source.


<c1adapter:C1RiaAdapter x:Name="c1Adapter" DataGrid="{Binding Data, ElementName=employeeC1DataGrid}">  
    <ria:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance local:Employee, CreateList=true}" Height="0" LoadedData="employeeDomainDataSource\_LoadedData" Name="employeeDomainDataSource" QueryName="GetEmployees\_Orders" Width="0">  
        <ria:DomainDataSource.DomainContext>  
            <local:NorthWindDomainContext />  
        </ria:DomainDataSource.DomainContext>  
    </ria:DomainDataSource>  
</c1adapter:C1RiaAdapter>  
<c1dg:C1DataGrid ItemsSource="{Binding Mode=OneWay, ElementName=c1Adapter, Path=Data}" Name="employeeC1DataGrid" Grid.Row="0"  LoadedRowDetailsPresenter="employeeC1DataGrid_LoadedRowDetailsPresenter">  
    <c1dg:C1DataGrid.RowDetailsTemplate>  
        <DataTemplate>  
            <c1dg:C1DataGrid MaxHeight="150" />  
        </DataTemplate>  
    </c1dg:C1DataGrid.RowDetailsTemplate>  
</c1dg:C1DataGrid>  

About this markup: The ComponentOne DataGrid supports two ways to do "codeless" (XAML) binding to a RIA Services DomainDataSource. You could do a direct binding to the DomainDataSource, however with this approach you won't get C1DataGrid's built-in filtering. This is because RIA Services uses a different approach to filtering than just a standard Collection View. We need to use the additional C1RiaAdapter to translate the filtering information so RIA Services can still perform filtering. So rather than binding to the DomainDataSource, we bind to the adapter, and put the DDS inside. It's really not that tricky. If you don't need filtering, then you don't need to use the C1RiaAdapter for any other purpose. The DomainDataSource's QueryName is set to the GetEmployees_Orders call we previously created. Before compiling this markup we need to add some event logic in our code.


private void employeeDomainDataSource_LoadedData(object sender, LoadedDataEventArgs e)  
{  
    if (e.HasError)  
    {  
        System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);  
        e.MarkErrorAsHandled();  
    }  
}  

The employeeDomainDataSource_LoadedData event fires after the data has attempted to load. This event is most useful to display an error message, as demonstrated above. This will also help with troubleshooting issues surrounding your data connection.


private void employeeC1DataGrid_LoadedRowDetailsPresenter(object sender, C1.Silverlight.DataGrid.DataGridRowDetailsEventArgs e)  
{  
    if (e.Row.DetailsVisibility == Visibility.Visible)  
    {  
        Web.NorthWindDomainContext ds = new NorthWindDomainContext();  
        EntityQuery<EmployeeTerritory> query = ds.GetEmployeeTerritoriesQuery().Where(et => et.EmployeeID == int.Parse(employeeC1DataGrid.GetCell(e.Row.Index, employeeC1DataGrid.Columns["EmployeeID"].DisplayIndex).Text));  
        ds.Load<EmployeeTerritory>(query, false).Completed += (\_sender, \_e) =>  
        {  
            List<EmployeeTerritory> list = ((LoadOperation<EmployeeTerritory>)_sender).Entities.ToList();  
            C1DataGrid rowDetailsDataGrid = e.DetailsElement as C1DataGrid;  
            rowDetailsDataGrid.ItemsSource = list;  
        };  
    }  
}  

The employeeC1DataGrid_LoadedRowDetailsPresenter event fires when the C1DataGrid needs to display the row details. We are using this event to dynamically load Employee Territory information when the user expands the row details for any employee. This virtualization technique is great because we are only fetching data as we need to. If you notice in the XAML markup above, we are hosting a second C1DataGrid inside the RowDetails template of the first C1DataGrid. Inside this LoadedRowDetailsPresenter event we are calling our DomainDataSource via a NorthwindDomainContext object (which was automatically generated for us), and we use some basic LINQ to grab the desired records from our data context (where the EmployeeID matches the selected employee). We take the returned data and feed it to our details grid. When we run this, we see the RowDetails lazy-loading in action. When you initially click the Expand icon in the row headers, you'll notice a slight pause as the Employee Territory information is being fetched. You'll also notice that we have this strange blank Employee column in our child datagrid. Where is this coming from? Well, when we used LINQ to Entities to autogenerate our application logic, it added an "Employee" property onto our EmployeeTerritory object because it noticed that relationship. We could remove the Employee property in our metadata or explicity define the columns in C1DataGrid to just include the ones we want. That's all we need to do to work with RIA Services, LINQ to Entities and a hierarchical C1DataGrid. But we can extend this sample by adding another detail datagrid with child to display Orders and Order Details. In the full sample we set up Parent-Child/Child-Grandchild hierarchy something like this: > Parent (Employees) > Child (EmployeeTerritories) > Child (Orders) > GrandChild (Order Details) Download the sample below to see this additional implementation, as well as everything demonstrated in this blog post.

Download Sample

comments powered by Disqus