次の方法で共有


Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 18: Custom Linq Provider

Continuing in our discussion of Silverlight 3 and  the update to .NET RIA Services.  I have been updating  the example from my Mix09 talk “building business applications with Silverlight 3”.   RIA Services is very much an extension of the LINQ project.  Effectively you can think of RIA Services as n-tier LINQ.  As such, I thought it would be interesting to show how any of the tons of Linq Providers can be used with RIA Services. 

You can watch the original  video of the full session

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (Which includes Sql Express 2008)
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview

Also, download the full demo files and check out the running application.

Here is the general application pattern we are looking at this time:

image

Way back in Part 8: WCF I showed off how to get your data from a WCF services (rather than Entity Framework) via RIA Services.    One part I was never quite happy with is that I needed to pass the page number into the Query method.  While David Poll and I developed a neat little paging pattern that worked with this, it certainly felt like a hack.. and Jason Allor, our development manager totally agreed.  The core problem is I didn’t have a IQueryable that knew about my WCF services to return.  Jason assured me this would be easy to do based on the LINQ to TerraServer Provider Sampleand Matt Warren’s excellent set of posts.  So I dared him to try it and he did!

What Jason ended up with is pretty cool.  It does not cover 100% of the cases that RIA Services might use, but it does cover a lot or them and all the source is  here, so feel free to use and extend.

In my original WCF example, my query method looked like:

 public IQueryable<SuperEmployee> GetSuperEmployees(int pageNumber)
 {
     return this.Context.GetSuperEmployees(pageNumber)
                .Where(emp => emp.Issues > 100)
                .OrderBy(emp => emp.EmployeeID)
                .Select(emp =>
                    new MyApp.Web.SuperEmployee()
                    {
                        EmployeeID = emp.EmployeeID,
                        Gender = emp.Gender,
                        Issues = emp.Issues,
                        LastEdit = emp.LastEdit,
                        Name = emp.Name,
                        Origin = emp.Origin,
                        Publishers = emp.Publishers,
                        Sites = emp.Sites,
                    }).AsQueryable();
 }

Notice that page number should really be handled by skip() and take() in the query rather than an explicit parameter that doesn’t compose well. 

And with Jason’s cool new IQueryable  to WCF implementation it is much clearner:

 public IQueryable<SuperEmployee> GetSuperEmployees()
 {
     return new LinqToSuperEmployeeService(this.context)
                .Where(emp => emp.Issues > 100);
 }

Basically the LinqToSuperEmployeeService parses the Linq query and gives us access to the individual parts of the query.  

 

 internal class LinqToSuperEmployeeService : QueryableService<SuperEmployee>
 {
     private SuperEmployeeServiceClient context;
  
     public LinqToSuperEmployeeService(SuperEmployeeServiceClient context)
     {
         this.context = context;
     }
     protected override object ExecuteQuery(QueryDetails details)
     {
         if (details.Count)
         {
             return this.context.GetSuperEmployeesCount(
                 details.SkipSize, 
                 details.PageSize, 
                 details.OrderBy,
                 details.Filters.ToArray());
         }
         else
         {
             return this.context.GetSuperEmployees(
                 details.SkipSize, 
                 details.PageSize, 
                 details.OrderBy,
                 details.Filters.ToArray())
            .Select(emp => ConvertUtils.Convert(emp));
         }
     }
 }

For example, consider a query such as:

 q.Skip(20).Take(10)
  .OrderBy(e => e.Name)
  .Where(e => e.Origin == "Earth");

 

We’d end up calling the GetSuperEmployees(20,10,null,{“Earth”});

The base QueryableService will parse out the the SkipSize, PageSize and orderby information an puts it in the QueryDetails class.  And then in the this overload of the Execute method we pluck those out and pass them on to the WCF service.   You could just as easily pluck those values out and call a REST based service or generate some TSQL, etc. 

If you wanted to reuse this functionality for your WCF service, you just derive your own subclass of QueryableService and do the right thing with the values.

One more cool thing, if you create your own subclass of of DomainService that can handle calling a particular WCF service things get even easier.

 [EnableClientAccess()]
  public class SuperEmployeeDomainService : LinqToSuperEmployeeDomainService
  {
      public IQueryable<SuperEmployee> GetSuperEmployees()
      {
          return this.Context;
      }

And the code for your custom DomainService?  Pretty easy as well.

 public class LinqToSuperEmployeeDomainService : DomainService
 {
     private SuperEmployeeServiceClient webServiceContext = new SuperEmployeeServiceClient();
  
     private LinqToSuperEmployeeService linqContext;
  
     protected LinqToSuperEmployeeDomainService()
     {
         this.linqContext = new LinqToSuperEmployeeService(this.webServiceContext);
     }
  
     public IQueryable<SuperEmployee> Context
     {
         get { return this.linqContext; }
     }
  
     public SuperEmployeeServiceClient WebServiceContext
     {
         get { return this.webServiceContext; }
     }
 }

This part talked about how to use a custom Linq provider to make it very easy to call a WCF service to get data into your Silverlight client.  The very simple Linq provider I show here can be easily customized to work with any service or other data source.

Comments

  • Anonymous
    August 04, 2009
    Concerning you authentication. Once a user is logged via Silverlight... does that mean the .aspx pages are also logged in? If a user logged in using the .aspx membership system could that user details be shared with Silverlight? Just wondering how a hybrid system of .aspx and Silverlight would work with authentication.

  • Anonymous
    August 04, 2009
    My app consists of a complex model with 2 string fields and an IQueryable<Muscles> musList.   Muscles is a model class.  musName is a field within the Muscle list. I am using the RIA to WCF services as in part 8 with a RIA:DomainDataSource in the XAML and Bind it to a DataGrid. I get data for the 2 string fields, but can not find a way to Bind to the Muscle list.  The col in the grid that I am binding to is a custom template with a <ListBox> control.  The grid is bound to the RIA and the grid col is defined as follows... <data:DataGridTextColumn Header="Nerves/Muscles"  Binding="{Binding Muscles}" />                                    <data:DataGridTemplateColumn >                                        <data:DataGridTemplateColumn.CellEditingTemplate>                                            <DataTemplate>                                              <ListBox ItemsSource="{Binding Muscles}">                                                    <ListBox.ItemTemplate>                                                        <DataTemplate>                                                            <TextBlock Text="{Binding Path=musName}"></TextBlock>                                                        </DataTemplate>                                                    </ListBox.ItemTemplate>                                                </ListBox>                                              </DataTemplate>                                        </data:DataGridTemplateColumn.CellEditingTemplate>                                    </data:DataGridTemplateColumn>

  • Anonymous
    August 04, 2009
    Is it just me or is the sourcecode download not working? I really like the way .net RIA is going!

  • Anonymous
    August 05, 2009
    Hi, Nice article. I wonder how to pass the paging parameters from silverlight ? Thanks, Thani

  • Anonymous
    August 05, 2009
    The expected List is populated and passed in the RIA data object.  I think the problem is that I don't know the proper XAML binding syntax for attaching the RIA's embedded list to the grid.  This simple properties are easy to bind with simple XAML bind features.

  • Anonymous
    August 05, 2009
    @Thanigainathan: The paging details are passed from client to server through the query, using .Take and .Skip clauses in the query. This is identical to what happens when you have Linq2SQL, Astoria, or any other data provider on the server which supports IQueriable

  • Anonymous
    August 05, 2009
    Current download MyApp.L2S.zip is not same as what is shown in post, it is missing QueryableService and other related code...

  • Anonymous
    August 06, 2009
    Manish  - Ahh, great.. thanks for letting me know.. I fixed it.  Please try again

  • Anonymous
    August 13, 2009
    When sorting on EmplayID: There is no method 'ThenBy' on type 'System.Linq.Enumerable' that matches the specified arguments

  • Anonymous
    August 15, 2009
    I´m prefer Adobe Flex, but SilverLight is not bad!

  • Anonymous
    August 21, 2009
    The comment has been removed

  • Anonymous
    September 11, 2009
    Hi all, I am using WCF and Entity Framework with RIA services my EntityFramework edmx file is in a separate layer called DataAccess and my services are separate projects in the same solution. In my web project i am having two service reference as i am using two services admin service and public service which use entity framework. Now consider that i have a table Person in the EF entity model and another Person i defined as a Ria Domain object so both are different but when i want to persist some changes using the services then my ria Person objects needs to be converted to ServiceReference Person Object, so i need two methods which convert each other i.e Ria to EntityObject and Entity to Ria object. So my question is if i have 200 such objects do i have to write 200 such conversions. Given below is the extract from your demo of using Ria with WCF and EF SuperEmployee Convert(MyApp.Web.SuperEmployeeServiceReference.SuperEmployee wsEmp)        {            var emp = new SuperEmployee();            emp.EmployeeID = wsEmp.EmployeeID;            emp.Gender = wsEmp.Gender;            emp.Issues = wsEmp.Issues;            emp.LastEdit = wsEmp.LastEdit;            emp.Name = wsEmp.Name;            emp.Origin = wsEmp.Origin;            emp.Publishers = wsEmp.Publishers;            emp.Sites = wsEmp.Sites;            return emp;        }        MyApp.Web.SuperEmployeeServiceReference.SuperEmployee Convert(SuperEmployee emp)        {            var wsEmp = new MyApp.Web.SuperEmployeeServiceReference.SuperEmployee();            wsEmp.EmployeeID = emp.EmployeeID;            wsEmp.Gender = emp.Gender;            wsEmp.Issues = emp.Issues;            wsEmp.LastEdit = emp.LastEdit;            wsEmp.Name = emp.Name;            wsEmp.Origin = emp.Origin;            wsEmp.Publishers = emp.Publishers;            wsEmp.Sites = emp.Sites;            return wsEmp;        }

  • Anonymous
    September 22, 2009
    I want to expose only my DTO's to the client. And I want to the DTO's to be IQueryable. The backend is LinqToEntitiesDomainService. The mapping between  columns of the ef-entities and the DTOs are not always one-to-one. E.g.: "....ETD = DateTimeToIntConverter.TryConvertDateAsIntToDateTime(e.U_PHDATO, e.U_PHTID)..." ..where U_PHDATO and U_PHTID are integers. Is this possible ? I get errors if I use the way as the DTO sample - linq doesn't know how to translate this. Could you give a sample with EF backend + DTO, and "custom mapping" between ef and dto ?