Advertisement

RIA Services and relational data

As we’ve all been guilty, when you see demonstrations of technologies most of the time the data samples show single table solutions.  When was the last time you’ve developed a single-table system? :-)  Thought so.

In RIA Services demonstrations, most of them have been single-table samples as well.  So how do you go about retrieving relational data (master/details type) with RIA Services?  Here’s an option.  I’m using VS2010, Silverlight 4 and the WCF RIA Services preview using the below sample.  I’m also using the Chinook sample database which has become one of my favorite simpler relational data samples to use.

Creating your project and associated RIA Services

This is easy, create a new Silverlight project and make sure the ‘Enable .NET RIA Services’ link is checked (yes, we know it doesn’t say WCF in that dialog).  My Silverlight application will be a simple button to retrieve artists then show their associated albums.  Here’s my XAML to start:

   1: <Grid x:Name="LayoutRoot" Background="White">
   2:     <StackPanel Width="400">
   3:         <Button Content="Get Artist Information" x:Name="GetArtistButton" Click="GetArtistButton_Click" />
   4:         <StackPanel Orientation="Horizontal">
   5:             <StackPanel x:Name="ArtistsContext">
   6:                 <StackPanel Orientation="Horizontal">
   7:                     <TextBlock Text="Artists: " />
   8:                     <TextBlock Text="{Binding ElementName=ListOfArtists, Path=Items.Count}" />
   9:                 </StackPanel>
  10:                 <ListBox x:Name="ListOfArtists" Width="200" Height="300" DisplayMemberPath="Name" ItemsSource="{Binding}"/>
  11:             </StackPanel>
  12:             <StackPanel x:Name="AlbumsContext">
  13:                 <StackPanel Orientation="Horizontal">
  14:                     <TextBlock Text="Albums: " />
  15:                     <TextBlock Text="{Binding ElementName=ListOfAlbums, Path=Items.Count}" />
  16:                 </StackPanel>
  17:                 <ListBox x:Name="ListOfAlbums" DisplayMemberPath="Title" ItemsSource="{Binding}" Width="200" Height="300"/>
  18:             </StackPanel>
  19:         </StackPanel>
  20:     </StackPanel>
  21: </Grid>

Now on the server side I need to create the associated models and domain services to be consumed.  I’m creating my model using Entity Framework and it looks like this:

Chinook Entity Model

Now I need to create my Domain Service class for that model (remember to build the solution after you create your model so it will show up in the tools).  When we create the Domain Service class be sure to enable the checkbox to generate associated classes for metadata.  Once we finish we have some stub services created for us. 

Using the Domain Service functions

We have GetArtists and GetAlbums functions we can work with.  As an example we can wire up the button click to retrieve a list of artists using the default functions we got:

   1: ChinookContext ctx = new ChinookContext();
   2:  
   3: private void GetArtistButton_Click(object sender, RoutedEventArgs e)
   4: {
   5:     ArtistsContext.DataContext = ctx.Artists;
   6:     ctx.Load(ctx.GetArtistsQuery());
   7: }

But what about when a user clicks on an Artist, we want to show the albums for that artist and not the others.  We need to modify our Domain Service to add a function:

   1: public IQueryable<Album> GetAlbumsForArtist(int ArtistId)
   2: {
   3:     return this.ObjectContext.Albums.Where(a => a.ArtistId == ArtistId);
   4: }

Now we can use that function when a user clicks on an associated artist to populate the album information:

   1: private void ListOfArtists_SelectionChanged(object sender, SelectionChangedEventArgs e)
   2: {
   3:     ListBox theList = sender as ListBox;
   4:     Artist a = theList.SelectedItem as Artist;
   5:     ctx.Albums.Clear();
   6:     AlbumsContext.DataContext = ctx.Albums;
   7:     ctx.Load(ctx.GetAlbumsForArtistQuery(a.ArtistId));
   8: }

Cool.

However, the second event handling for our master-details section for this particular data set seems unnecessary.  After all, why not just include the children data with our initial request if we *know* that we’re doing an explicit master-details view (and our set is not that large relatively speaking).

Modify the metadata classes

Remember the generated metadata classes?  Go back to it now.  You’ll see a definition of the ArtistMetadata that includes this:

   1: public EntityCollection<Album> Albums;

Notice it has an Albums collection property.  Great, so we could just modify our XAML binding to use some element binding and get the Albums property of the SelectedItem right?  Well, not yet.  If we do that, we’ll have no data.  Why is that?  Because we haven’t told RIA Services to perform the necessary additional query to get the data.  Simple add [Include] at the top of the Albums collection:

   1: [Include]
   2: public EntityCollection<Album> Albums;

And that’s what we need.  Now we can add a function to our Domain Service class to get the additional data:

   1: public IQueryable<Artist> GetArtistsWithAlbums()
   2: {
   3:     return this.ObjectContext.Artists.Include("Albums");
   4: }

Now we just need to do some clean up.  We need to change our button click code to get the GetArtistsWithAlbums query now instead of the other one first.

Remove unnecessary code and use binding to help us

Now we can remove the SelectionChanged event handler for our Artists ListBox as well as add some binding commands to our XAML like this:

   1: <Grid x:Name="LayoutRoot" Background="White">
   2:     <StackPanel Width="400">
   3:         <Button Content="Get Artist Information" x:Name="GetArtistButton" Click="GetArtistButton_Click" />
   4:         <StackPanel Orientation="Horizontal">
   5:             <StackPanel x:Name="ArtistsContext">
   6:                 <StackPanel Orientation="Horizontal">
   7:                     <TextBlock Text="Artists: " />
   8:                     <TextBlock Text="{Binding ElementName=ListOfArtists, Path=Items.Count}" />
   9:                 </StackPanel>
  10:                 <ListBox x:Name="ListOfArtists" Width="200" Height="300" DisplayMemberPath="Name" ItemsSource="{Binding}"/>
  11:             </StackPanel>
  12:             <StackPanel x:Name="AlbumsContext" DataContext="{Binding ElementName=ListOfArtists, Path=SelectedItem}" >
  13:                 <StackPanel Orientation="Horizontal">
  14:                     <TextBlock Text="Albums: " />
  15:                     <TextBlock Text="{Binding ElementName=ListOfAlbums, Path=Items.Count}" />
  16:                 </StackPanel>
  17:                 <ListBox x:Name="ListOfAlbums" DisplayMemberPath="Title" ItemsSource="{Binding Albums}" Width="200" Height="300"/>
  18:             </StackPanel>
  19:         </StackPanel>
  20:     </StackPanel>
  21: </Grid>

Notice how the DataContext of my Albums ListBox is now set using element binding to the SelectedItem of the Artists ListBox.  Then the ItemsSource of the ListBox for Albums has a {Binding Albums} command.  This is because our Artists query now includes the associated Album data and we can just reference the property.

Use with caution

While this example shows how easy it can be to have included results in your Domain Service query result, be mindful of when you are using.  For instance if you have a customer database of 1000 customers and you want all orders to be retrieved…it might not be wise to use this particular type of method. 

This presents merely another choice for areas where you may want/need it (i.e., country/state/city) for your application.

You can download the sample solution for the above code snippets here: SilverlightApplication41.zip.  Reminder that you will need to have the Chinook database installed already – it is NOT included with this sample download.

Hope this helps!


This work is licensed under a Creative Commons Attribution By license.

  1. 1/5/2010 9:37 PM | # re: RIA Services and relational data
    Excellent post and thanks for thinking of a 1/2 way real application scenario vs a simple technology demo.
  2. 1/5/2010 11:15 PM | # re: RIA Services and relational data
    Hi
    What about using a stored proc ?
  3. 1/5/2010 11:18 PM | # re: RIA Services and relational data
    Great timing, I was just watching your video on RIA services the other day and thinking about best practices for dealing with relational data. Thanks for filling the gap.
  4. 1/6/2010 12:28 AM | # re: RIA Services and relational data
    Thanks for the multi-table example but I second the call for Stored Procedure examples. Not just simple retrieval but inserts, updates and deletes as well.
  5. 1/6/2010 1:00 PM | # re: RIA Services and relational data
    Good post. I wish this had been written earlier as I spent a long time surfing around to figure this out. Another rarely mentioned issue which is worth going over is how to do many-to-many and include the results with RIA. The Entity Framework doesn't auto-generate a compatible model and RIA is very picky about exactly how you need to fix it.
  6. 1/6/2010 1:27 PM | # re: RIA Services and relational data
    Hello, this is nice post, but I also miss "many-to-many". How can I do it with RIA? Is there some article on net on this problem? Thanks.
  7. 1/6/2010 2:40 PM | # re: RIA Services and relational data
    When you call this.ObjectContext.Artists.Include("Albums") are those album objects stored in MyDataContext.Albums?
  8. 1/6/2010 3:19 PM | # re: RIA Services and relational data
    John, you'll notice the function is IQueryable<Artist> GetArtistsWithAlbums. So when that function is called you'd get a list of Artist, which would include their Artist.Albums property where .Albums is EntityCollection<Album>. Make sense?
  9. 1/7/2010 3:58 AM | # Your mission Tim, should you decide to accept it...
    Great article: after poking around with RIA Services since July we're finally seeing some decent samples.

    Now a couple of things I am still looking for:

    a) a many-to-many example (preferable built of EF4, eg. User <> Roles)

    b) a good reason why entities cannot be shared between domain services. This is a biggie as it means you either have to have a MONSTER domain service with all possible entities within it... or break out your code into seperate libraries. This would be a good thing but complicates modelling (I use model-first). Or am I missing something?
  10. 1/7/2010 4:03 PM | # re: RIA Services and relational data
    great post
    but i am looking for more
    how to insert update and delete

    also many to many sample
    I need these things badly

    thank u
  11. 1/8/2010 1:59 PM | # re: RIA Services and relational data
    This is a bit off topic, but do you know if it's possible to run .NET Ria Services (not WCF ria Services) in VS10 beta? I am trying to do this because I cannot upgrade to WCF at the moment (my hosting does not support it) but I would like to use VS 10 for development.

    My experiance is that after a project upgrade (keeping the framework at 3.5) in VS 10, I have reference errors that I cannot resolve. In particular, "Ria does not exist in the namespace 'System.Web' yet the reference to System.Web.Ria is there with a version of 2.0., as well as DomainServices and DataAnnotations.

    If this can't be done, that's ok, but I thought I would give it a shot.

    Greg
  12. 1/8/2010 2:12 PM | # re: RIA Services and relational data
    Greg - no it is not possible.
  13. 1/8/2010 7:48 PM | # re: RIA Services and relational data
    LOL, ok well thanks anyway, I can stop trying now...
  14. 1/10/2010 3:10 AM | # re: RIA Services and relational data
    Tim,

    Great article. Do you know if it would be possible to 'include' Navigation properties on an object like Artist? That is, if Artist were to have a Navigation Property called 'ExHubbyCollection", could you include the ExHubbyCollection in the initial call?
  15. Gravatar
    1/11/2010 8:23 AM | # re: RIA Services and relational data
    Tim,
    This is an off-topic question. When is it going to be possible to develop Silverlight applications for the iPhone (using VS.NET)?
  16. 1/12/2010 7:52 PM | # re: RIA Services and relational data
    Tom -- as soon as iPhone platform enables it we can begin to explore the tooling and necessary changes we would need to make.
  17. 1/15/2010 10:19 PM | # re: RIA Services and relational data
    Thanks you, great article. I've been struggling for two months trying to do this with DataGrid. While I still can't get associated data to show in a DataGrid, the TextBox, ListBox, and ComboBox's work fine.
  18. 1/20/2010 12:35 AM | # re: RIA Services and relational data
    Hi Tim
    An associated question - is it possible to persist data in an out of browser app which is disconnected?
  19. Gravatar
    1/22/2010 8:08 AM | # re: RIA Services and relational data
    Hey Tim,
    love your posts.
    Is there a way to have my own framework(DTO, DALC, ...) while using RIA?! My assumption is that I have to use entity models to be able to use RIA.
    Thanks,
  20. 1/26/2010 6:01 PM | # re: RIA Services and relational data
    Lets say I have a query which returns data from across two tables which have a Foreign/Primary Key relationship

    i.e

    ServerGroup

    ServerGroupID

    ServerGroupName



    Server

    ServerGroupID

    Hostname


    And a query in my domain service like this

    public IQueryable<ServerFarm> GetServerFarmbyID(in ServerGroupID)
    {
    return this.ObjectContext.ServerGroup.Include("Server").Where(c => c.ServerGroupID.Equals(ServerGroupID));
    }

    So I should get all of the "Server" table entries as well as the "ServerGroup" that matches the "ServerGroupID" passed.

    Can I then in a Dataform datafield bind the fields individually of the "Server"?

    So something like


    <dataForm:DataField Label="Hostname">
    <TextBox Text="{Binding Hostname, Mode=TwoWay}" />
    </dataForm:DataField>

    The reason I want to do this, is because if I query the "Server" table on its own, then when I add a New "Server" entitiy through the dataform the "ServerGroupID" isn't set. I was hoping if I do it this way, there would be some smarts which would automatically fill in the "ServerGroupID" for me?
  21. 1/28/2010 2:53 AM | # re: RIA Services and relational data
    How about LinqToSql ? I can't find the Include function !!!
  22. 2/2/2010 2:38 PM | # re: RIA Services and relational data
    Tim:

    Does this work with SL3 Ria WCF Services?
    I am trying it with two DataGrids, rather than list boxes as you have and the child grid is not displaying any data.
  23. 2/2/2010 5:15 PM | # re: RIA Services and relational data
    Stacy -- it should. Make sure you put the attributes correctly and modify your methods to return the query.
  24. 2/15/2010 11:25 AM | # re: RIA Services and relational data
    Hi Tim,
    I can get this to work with a single foreign key relationship, but how do I do this for more than one table, both foreign keyed to the primary table?
    -Erin
  25. 2/18/2010 3:05 PM | # re: RIA Services and relational data
    Hi Tim,
  26. 2/18/2010 3:10 PM | # re: RIA Services and relational data
    What about using Stored Procedures in your example? can u help me with this??? could be by email.. thanks man!

    Great Post
  27. 2/19/2010 8:21 AM | # re: RIA Services and relational data
    I've seen the questions about the many to many relationships, and I'm wondering if there is a solution for this. I've been searching as of yet cannot find anything.
  28. 3/3/2010 8:00 AM | # re: RIA Services and relational data
    Thanks Tim for a very useful post.

    For all of you that has been looking for an option to include more than one foreign key, I cannot find any support for this either.

    That's the bad news.....but AlexJ at Meta-Me has produced some very useful Include extensions that makes it possible to do both sub-includes and multiple includes.

    Have a look here: blogs.msdn.com/.../...ment-include-strategies.aspx

    Have tried it - works like a charm...

  29. 3/3/2010 9:09 AM | # re: RIA Services and relational data
    Similar to what Cliff Eby describes in his post Jan 15, I cannot get this to work with DataGrid using AutoGenerateColumns="False".

    This one won't work, even if the datacontext contains information for "Albums.ID":

    <data:DataGridTextColumn x:Name="IDColumn" Binding="{Binding Path=Albums.ID}" Header="ID" />

    If I use AutoGenerateColumns="True", and bind the ItemSource to the included Entity (Albums), it works like a charm.

    If anyone has found the solution to this, please feel free to post some sample code.
  30. 3/4/2010 1:18 PM | # re: RIA Services and relational data
    Solved by adding ItemsSource = "{Binding Albums}" to the DataGrid, and changing the columns binding from Binding="{Binding Path=Album.ID}" to Binding="{Binding Path=ID}"

 
Please add 3 and 7 and type the answer here:
First time here? You are looking at the most recent posts. You may also want to check out older archives. Please leave a comment, ask a question and consider subscribing to the latest posts via RSS or email. Thank you for visiting! (hide this)