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}"
  31. 4/26/2010 1:21 AM | # re: RIA Services and relational data
    Hi Tim

    First, thank you for all your helping posts. I read them all. Could you help solving me the following problem?

    I wish to convert SL3 WCF RIA application into SL4. At the momemnt I am trying to get data into a datagrid from the third relational tabel which is related to the second. I have SL4 and using WCF RIA.

    In SL3 I have included the third table into service like:
    ....Include("MemberDetail.CommonData")

    and included columns in service metadata like:
    [MemberDetail("FirstName","FirstName")]
    [MemberDetail.CommonData("Town","Town")]
    public MemberDetail MemberDetail {get; set;}

    In dataGrid cloumnc binding I had:
    ....Path=FirstName....
    ....Path=Town....

    I works OK in SL4 too, however I wish to use more elegant way. When I put the same "include" into service and the following:

    [Include]
    public MemberDetail MemberDetail {get; set;}

    into service metadata, I can get all the columns from the relational table into dataGrid with bindings like
    ....Path=MemberDetail.FirstName....

    but not for the third table, with the following:
    ....Path=MemberDetail.Common.Town....

    I suppose I have to add some "Include" into service metadata, but I do not know how.




  32. 4/28/2010 9:52 AM | # re: RIA Services and relational data
    Solved, by adding [Include] in a second entity matadata class too
  33. 5/13/2010 3:50 AM | # re: RIA Services and relational data
    Any news on how we carry out this functionality with a many-to-many relationship scenario?

    Entity Framework abstracts away my link entity but then errors when I attempt to use the .Inlcude() functionality.
  34. Gravatar
    5/27/2010 7:50 AM | # re: RIA Services and relational data
    Multiple foreign keys are actually pretty easy.
    In your DomainService.metadata.cs, decorate your sub-entities with the [Include].

    Then in your DomainService.cs, in the IQueryable of your main entity, add the appropriate includes for your sub entities.

    return this.ObjectContext.main_entity.Include("foreign_key_entity1").Include("foreign_key_entity2").Include("foreign_key_entity3").Include("foreign_key_entity4")

    You can even do this on lower level entities
    return this.ObjectContext.main_entity.Include("sub_entity.foreign_key1.foreign_key1a").Include("sub_entity.foreign_key2.foreign_key2a").Include("sub_entity.foreign_key3.foreign_key3a")

    Then, compile, go to the datasources in the designer, expand your main entity and drag the related entities (that are included as part of your main entity) to your work surface.

    Write your submit changes button.
    private void saveButton_Click(object sender, System.Windows.RoutedEventArgs e)
    {
    routeMasterStepDomainDataSource.SubmitChanges();
    }

    And Bam! All the child tables tables are updated automagically.

    Now I just need to know how to turn a foreign key into a combobox in Silverlight 4 using visual studio 2010.
  35. 6/19/2010 9:09 AM | # re: RIA Services and relational data
    I am getting an error when adding the [Include] attribute to the domain service class

    Error 1 Attribute 'Include' is not valid on this declaration type. It is only valid on 'property, indexer, field' declarations.
  36. 6/19/2010 9:13 AM | # re: RIA Services and relational data
    Ooops - my mistake: should have been in the metadata class :-)
  37. 7/27/2010 11:16 AM | # re: RIA Services and relational data
    Tim,

    Will the [Include()] attribute work in a separate partial class file outside of the autogenerated RIA Services metadata file?

    If you regenerate the domain service class, won't the metadata file erase any custom [Include()] attributes you decorate to any properties or entity collections?

    thanks,
    John
  38. 8/4/2010 7:20 PM | # re: RIA Services and relational data
    Disculpa mi Ingles

    En cual archivo de meta dato tengo que poner [Include]

    Gracias
  39. 8/26/2010 8:25 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. somehow it wouldn't translate when i'm using firefox, but I'm able to understand the article as it's not that deep. Interesting though..thanks.
  40. 8/28/2010 10:27 AM | # re: RIA Services and relational data
    Tim,

    Have used this technique before in SL3 etc. BUT when we have to rebuild the DomainService - ie add new data table into the model etc. - these changes are lost.

    I was hoping that Entity Framework 4 would fix this issue - but you still seem to be recommending that we modify auto-generated code :-((. Sureky that should be a NO-NO!!.

    Same goes for adding new methods - the examples always show adding this to the auto-generated domainservice code files - why ??.

    Surely there must be a cleaner way to do this by now ?? - am I missing something here ??.

    I have worked around this in the past by creating a GSDomainService as a wrapper - which inherits the auto-generated class, then add my methods into my own file. Its KLUNKY but it works for new methods but NOT for the [Include] attribute :-(.

    NB Your articles and video's are always helpful and are most welcome ;-).

    Thanks

    Graham
  41. 9/2/2010 2:40 PM | # re: RIA Services and relational data
    This is probably a bad idea with large data. but i was having an issue getting entities of entities. i was able to get it to return all my data by making my query look like this:
    <Query(IsDefault:=True)> _
    Public Function GetProjects() As IQueryable(Of Project)
    Dim RE As New ResourceEntities
    Return RE.Projects.AsQueryable
    End Function

    i have all my include tags and everything, not sure if i still needed them to get that.

 
Please add 1 and 4 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)