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!