Partager via


Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 21: Hierarchical Data

Wow – the gift that keeps giving.. I am *still* having fun updating my my simple Mix 09 Business Application demo.   Would anyone be interested in a say 10 hour session at PDC2009 where we walk thought all of this stuff ;-).  Anyway, in this section I want to take up a challenge that Ben Hayat sent me to show that Silverlight and  RIA Services are really capable of building real business applications.  That was too good bait for me to pass up.     Apparently real business applications include more than one table, and even more than simple master details.  But they also include hierarchal data.  For example you have an Orders table that has an associated set of line items in a separate LineItems table, but the Orders keeps track of total sales, net, tax and number of lines, as each line gets added or deleted. 

You can see the full series here.

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

  1. VS2008 SP1
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview

Also, download the full demo files

To fit this to my SuperHero Placement Service, let’s consider the business problem of keeping up with the very witty quotes SuperHero’s often make.. And further, let’s use some advanced AI-based scoring technology to rate each quote then have the SuperEmployee keep up with their total points count.

To do this, first let’s add a Quotes table

image

Then let’s add the associations

image

And setup the foreign key relationship.. One SuperEmployee can have Many quotes. 

image

Then we can refresh our Entity Framework model as we saw earlier and this gets easier to see…

image

Now we need set up the Quotes entity to return to the client. First, we need to tell Entity Framework to include Quotes in queries to the Database for SuperEmployee… 

 public IQueryable<SuperEmployee> GetSuperEmployees()
 {
     
     return this.Context.SuperEmployeeSet
                .Include("Quotes")
                .Where(emp=>emp.Issues>10)
                .OrderBy(emp=>emp.EmployeeID);
 }

Now, we need to make sure the table gets sent to the client, so in the SuperEmployeeDomainService.metadata.cs file, let’s add the Quotes property and mark it as included.  This ensures that we are sending the minimal information over the wire by default. 

 internal sealed class SuperEmployeeMetadata
 {
  
     // Metadata classes are not meant to be instantiated.
     private SuperEmployeeMetadata()
     {
     }
  
     [ReadOnly(true)]
     public int EmployeeID;
  
     [Include]
     public Quotes Quotes;

Now, we need to add an Insert method for quotes.. Notice we don’t need Query, or Add because we don’t support those operations.  If you wanted to update the SuperEmployee server entity you could do that here as well…

    1: public void InsertQuote(Quotes quote)
    2: {
    3:     this.Context.AddToQuotes(quote);
    4: }

Now, on the client,I wanted a very simple UI, so I replaced the DataForm with a ListBox to list all the quotes already attributed to this SuperEmployee and then a very simple TextBox for adding a new quote. 

    1: <StackPanel Margin="35,30,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Height="498" >
    2:     <ListBox x:Name="quotesList">
    3:         <ListBox.ItemTemplate>
    4:             <DataTemplate>
    5:                 <StackPanel Orientation="Horizontal" >
    6:                    <TextBlock Text="{Binding Quote}"></TextBlock>
    7:                     <TextBlock Text=" - "></TextBlock>
    8:                     <TextBlock Text="{Binding Points}"></TextBlock>
    9:                 </StackPanel>
   10:  
   11:             </DataTemplate>
   12:         </ListBox.ItemTemplate>
   13:     </ListBox>
   14:     <TextBlock Text="Quote:"></TextBlock>
   15:     <TextBox x:Name="newQuoteTextBox" Width="400" Height="25" TextWrapping="NoWrap" />
   16:     <Button Content="Ok" Click="Button_Click"> </Button>
   17: </StackPanel>

In lines 4-8 sets up a super simple DataTemplate for controlling how the quotes are displayed..  Then in lines 14-16 i setup a very simple form for adding a new quote. 

Now, to write up the data into the form.. I could have done this with DataBinding, but I sometimes, doing it in code is just as easy..

    1: private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    2: {
    3:     var emp = dataGrid1.SelectedItem as SuperEmployee;
    4:     quotesList.ItemsSource = emp.Quotes;
    5: }

Finally, I need to handle the button click event..

    1: Random random = new Random(DateTime.Now.Second);
    2: private void Button_Click(object sender, RoutedEventArgs e)
    3: {
    4:     var emp = dataGrid1.SelectedItem as SuperEmployee;
    5:     var q = new Quotes();
    6:     q.Quote = newQuoteTextBox.Text;
    7:     q.SuperEmployee = emp;
    8:     
    9:     //todo: do a real point generator.. 
   10:     q.Points = random.Next(0,10);
   11:     emp.Quotes.Add(q);
   12:     newQuoteTextBox.Text = "";
   13: }

Note in line 10, we use the very advanced AI logic to determine the quality of each quote… I am told American Idol is moving to this model now that Paula Abdul is leaving ;-) 

All the user needs to do now is hit Submit Changes and the the Insert method we wrote above is called for each item added. 

image

Now, we’d like to show the total points for each employee.. in this scenario I only need the data for UI display, so I going to use a computed property on the client. We do that with a partial class of the SuperEmployee class, where we define the TotalCount property in the obvious way (lines 12-18).  But we also need to raise a property changed notification when any new Quote is added.  So we sign up for the property change notification when the Employee is loaded and rise this event (lines 3-10)..  

    1: public partial class SuperEmployee 
    2: {
    3:     protected override void OnLoaded(bool isInitialLoad)
    4:     {
    5:         base.OnLoaded(isInitialLoad);
    6:         this.Quotes.EntityAdded += (s, e) =>
    7:             {
    8:                 this.RaisePropertyChanged("TotalPoints");
    9:             };
   10:     }
   11:  
   12:     public int? TotalPoints
   13:     {
   14:         get
   15:         {
   16:             return this.Quotes.Sum(q => q.Points);
   17:         }
   18:     }
   19: }

Then, we add a bit of xaml to the DataGrid and we get some our computed property..

    1: <data:DataGrid.Columns>
    2:    <data:DataGridTextColumn Header="Name" Binding="{Binding Name}" />
    3:    <data:DataGridTextColumn Header="Employee ID"  Binding="{Binding EmployeeID}" />
    4:    <data:DataGridTextColumn Header="Origin"  Binding="{Binding Origin}" />
    5:    <data:DataGridTextColumn Header="Total Points"  Binding="{Binding TotalPoints}" />   
    6: </data:DataGrid.Columns>

And the UI looks good and updates automatically. 

image

Finally, let’s do a bit more data validation.. 

First, on the server, in the SuperEmployeeDomainService.metadata.cs we define our buddy class.. that lets us hang additional metadata..

 [MetadataTypeAttribute(typeof(Quotes.QuotesMetadata))]
 public partial class Quotes
 {
     internal sealed class QuotesMetadata
     {
         private QuotesMetadata() { }
  
         [StringLength(140,
             ErrorMessage="Quote Text must be twitter length (less than 140 characters)")] 
         [Required]
         public string Quote;
     }
 }

Then on the client, we add some UI to display the error and some code to set the error text… 

 <TextBlock x:Name="errorBox" Foreground="Red" ></TextBlock>
    1: try
    2: {
    3:     var q = new Quotes();
    4:     q.Quote = newQuoteTextBox.Text;
    5:     var emp = dataGrid1.SelectedItem as SuperEmployee;
    6:     q.SuperEmployee = emp;
    7:  
    8:     //todo: do a real point generator.. 
    9:     q.Points = random.Next(0, 10);
   10:     emp.Quotes.Add(q);
   11:     newQuoteTextBox.Text = "";
   12: }
   13: catch (Exception validationException)
   14: {
   15:     errorBox.Text = validationException.Message;
   16: }

image

 

 

Great, so in this part we looked at how to deal with Hierarchal Data which is very common in the orders, order detail scenario.  We also showed how computed properties work.  Hope you enjoy!

Comments

  • Anonymous
    August 10, 2009
    The comment has been removed

  • Anonymous
    August 10, 2009
    Brad: your stuff in the last couple months has been nothing short of amazing. But, you need to spell correctly. Hierarchal is not a word. It's "Hierarchical":   http://www.google.com/search?hl=en&safe=off&rlz=1G1ACAWCENUS333&num=100&defl=en&q=define:hierarchical&ei=UrqASvOkMY_KsQOe2L32CA&sa=X&oi=glossary_definition&ct=title

  • Anonymous
    August 10, 2009
    Brad What would be the solution to getting the [Required] attribute to work with a non-string property?  At the moment it returns a very unfriendly message (data in wrong format).  I'm guessing this is caused by the data being converted before it's bound to the property, so the validation is never run in setter.

  • Anonymous
    August 10, 2009
    Great Series - Please don't forget the VB folks with any examples.

  • Anonymous
    August 10, 2009
    Brad, you ARE 'D' Man! Forget BradA, it should be BradD :-)

  • Anonymous
    August 10, 2009
    Brad, I am very impressed of RIA and your walkthroughs. I have some issues with RIA though. I have posted it on the Silverlight forum without any luck.

  1. My domain entities are contained in separate class library. How do I create the proxy classes in the web project without modifying the domain entities project ? Do I need to use WCF / ADO.Net services as backend to acheive this.
  2. Validation and dataform. How can I separate the validation rules (meta tags) from the class being validated and still be able to use the Dataform for validation ? I don't like the idea of mixing responsibilities (~the single responsibility principle).
  3. How to be able to use custom queries from viewmodel (filterdescriptors are sometimes too lightweight)  and still use domaindatasource to keep features likes paging etc.. I have tried interrupting the loading event  but that messes up the sorting / paging. Should domaindatasource be contained in view or viewmodel ?
  • Anonymous
    August 11, 2009
    Brad, Maybe you have addressed this in another post; however, do you have any idea on the timeframe for RIAs release? Thanks

  • Anonymous
    August 11, 2009
    @ Ron Cotton; Hi Ron; Take a look at this link. It might be helpful. http://silverlight.net/forums/t/101160.aspx ..Ben

  • Anonymous
    August 11, 2009
    Lars-Erik – I am glad you are liking the series..   >1. My domain entities are contained in separate class library. How do I create the proxy classes in the web project without modifying the >domain entities project ? Do I need to use WCF / ADO.Net services as backend to acheive this. Does this help? http://blogs.msdn.com/brada/archive/2009/07/28/business-apps-example-for-silverlight-3-rtm-and-net-ria-services-july-update-part-11-the-new-class-library-project.aspx   I think if you use the new RIA Services class Library project you should get the separation you want… >2. Validation and dataform. How can I separate the validation rules (meta tags) from the class being validated and still be able to use the >Dataform for validation ? I don't like the idea of mixing responsibilities (~the single responsibility principle). Here is an example of how to read the validation information from an Xml file..   does that help? http://code.msdn.microsoft.com/RiaServices/Release/ProjectReleases.aspx?ReleaseId=2659 >3. How to be able to use custom queries from viewmodel (filterdescriptors are sometimes too lightweight)  and still use domaindatasource >to keep features likes paging etc.. >I have tried interrupting the loading event  but that messes up the sorting / paging. >Should domaindatasource be contained in view or viewmodel ? Good questions..  I think of DomainDataSource as being a view thing…   Check out the pattern Nikhil uses for ViewModel and RIA Services here: http://www.nikhilk.net/TechEd09-South-Africa-Samples.aspx

  • Anonymous
    August 11, 2009
    thanks alot of for your about your efforts thanks,,,

  • Anonymous
    August 11, 2009
    thanks alot of for your about your efforts thanks,,,

  • Anonymous
    August 11, 2009
    I've had a good look at linq2sql and EF and I can't justify using them over stored procs. I know EF can use sprocs but it takes a lot away, so much in fact it doesn't seem worth using EF. I really like what you're starting with RIA services but I'd like to see how it could work with sprocs. On another note I tend not to have one object to each table in my data store but rather more like a ratio of 4:1 (it varies a lot). My objects represent use cases so i might have a widget in use case #1 with 5 properties and a readonlywidget in use case #2 with only 2 properties. Can RIA services help in this type of scenario?

  • Anonymous
    August 11, 2009
    Hi Brad, Quick question, we have just finished a ADO .NET Data Services for a RESTful API to our software and wanted to use it for a Silverlight widget. However Basic Auth (Credentials) is not supported in SL3. We are using Membership and Windows Auth and using the QueryInterceptors to return only the required data. It seems a shame to have to implement all the RIA stuff just to do some simple queries via a restful services. Are there plans to support Basic Auth in SL or is RIA the way forward and we should use this model. Is there a good workaround that we can use in SL3, Authenticating against a ADO .NET Data Service using basic Auth. Regards Tobi

  • Anonymous
    August 11, 2009
    The comment has been removed

  • Anonymous
    August 12, 2009
    Hi Brad as always nice article, however there is an issue, when you press Add New button, then input new data about superemployee, this information goes into DB however the grid doesn't show it. I created a post here about this http://silverlight.net/forums/t/118849.aspx Can you comment anything? Thx

  • Anonymous
    August 17, 2009
    The comment has been removed

  • Anonymous
    August 20, 2009
    The comment has been removed

  • Anonymous
    August 24, 2009
    It sure would be nice if you could change your examples from NorthWind to AdventureWorks...

  • Anonymous
    September 29, 2009
    Hi How would you manage link table scenarios. So, for example Superheros have many Powers but a Power can be had by many Superheroes. Thus a SuperheroPowers link table (SuperheroPowerId, ptrSuperheroId, ptrPowerId).   tx

  • Anonymous
    October 14, 2009
    Anyone found a resolution to "XamlServices not found" issue?  I entered it to Microsoft Connect but they closed it as "By Design" http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=402133

  • Anonymous
    October 18, 2009
    Hi Brad, Thank you very much for this tutorial. I have been struggling to get the Entity Framework to include hierarchical data. You have helped me to get the first level going, but I am struggling with subsequent levels in my database hierarchy. I was wondering if you could help... for example, if "Quotes" had a collection of objects called QuoteCollection, how would I get them at the same time? Something like...    return this.Context.SuperEmployeeSet              .Include("Quotes").Include("QuoteCollection") But as I'm sure you know that doesn't work! It would be great if you could offer some advice. thanks, Matt

  • Anonymous
    October 18, 2009
    Stay tuned - hierarchical data gets a lot easier in our PDC drop...

  • Anonymous
    October 18, 2009
    That's good news. Any rough estimate how long that'll take though?

  • Anonymous
    October 24, 2009
    I am new to RIA Services. There are a lot of great samples and tips in this series (going already for almost 9 month) But I did not find Silverlight 3  Accordion sample with Hierarchical data. How to show Categories in Accordion headers and relevant Products in content using, for example, Northwind Database,???

  • Anonymous
    November 04, 2009
    Brad: All your post are very nice, I'm learning a lot with this. I have a question, as you have this hierarchical data model but in a distributed environment like WFC (as in Part 8 "WCF Based Data Source"). Thank you very much for your help.