Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 17: Evolving an Application
More from my Mix09 talk “building business applications with Silverlight 3”. So far in this series we have looked at how to build out an new application using Silverlight 3 and .NET RIA Services… but now let’s look at how to evolve an existing SL3\RIA Services application… after all, for most of us that is our day-to-day jobs. It is rare that we get to start with a green field application where we have full control. Being able to deal with maintenance of an application takes a framework from being a cool demo toy to being an important tool!
For the context, you can watch the original video of the full session and catch up on the Full series
The demo requires (all 100% free and always free):
- VS2008 SP1 (Which includes Sql Express 2008)
- Silverlight 3 RTM
- .NET RIA Services July '09 Preview
Also, download the full demo files .
For this part, we will look at to interesting evolutions of the standard SuperEmployee app we have been building… The first is adding a new column to the table we have been working with and the second is to add a whole new table with additional data associated with each SuperEmployee.
A fair bit of the details of how the evolution workflow is accomplished is dependent on what DAL you use. Because .NET RIA Services is DAL agonistic, this part of the evolution story can change a bit. For this walkthrough I will show the story with Entity Framework and will leave it as an exercise for the reader to map it to your own DAL.
Adding a new Column
Ever since I cooked up the SuperEmployee schema 6 months ago I felt that it was really missing the most important aspect of being a super hero. That is knowing what his or her power is. I mean, i have to know their super power to hire them right? So let’s look at adding “Superpower” to the database.
Let’s open nortwind.mdf from the app_data directory and select Open Table Definition
Then add a new column called “SuperPower” and set it type to be nvarchar(MAX)
OK – we have the database scheme updated, now we need to update our model. To do that open northwind.edmx and select “Update Model from Database”
Select Refresh
And we see our model is updated!
Now, simply build and we can access this new property from the web server in the DomainService if we needed to write some application logic that deals with it or we can access it directly from the client.
To start with, let’s add SuperPower to the list of fields the DataForm is handling for us… This is just an update to the DataForm we talked about way back in Part 2: Rich Data Query
<dataControls:DataField>
<TextBox Text="{Binding SuperPower, Mode=TwoWay}" />
</dataControls:DataField>
And of course the display works great
But I can now add a value to several SuperHeros and hit submit and see the values reflected in the database.
Now let’s add just a bit of validation to our new member.. In the SuperEmployeeDomainService.metadata.cs file add
[StringLength(25,MinimumLength=3,
ErrorMessage = "Superpower's should be between 3 and 25 characters long")]
public string SuperPower;
build and run and we get get validation!
Let’s roll back to our Astoria service and our WinForm client from Part 5… what do we need to do to update those? Well, not much. The Astoria service is all dynamically generated based on the model and the DataGridView is also dynamic in this case, so all we need to do is update the service reference and we are good to go!
And the app runs great… an easy way to enter lots of data quickly..
What I showed so far is how easy it is to evolve your entity to have an addition field. Let’s now look at adding a whole new entity.
Adding a New Associated Entity
For this example, i’ll add some contact information for the SuperHero agent so that we can get up with the SuperHero we decide to hire. The first step is to add a table to our database to store the contact information
Then we need to associate it with the SuperEmployees table via an foreign key.
Now we can update our Entity Framework model
We want to select the new Contact class
And update our SuperEmployee class to get the FK for the Contact
That gives us this model
do a full build and everything should be good.
Now let’s integrate the Contact table into our app.
First we need to add the logic to our DomainService.
1: public IQueryable<SuperEmployee> GetSuperEmployees()
2: {
3: return this.Context.SuperEmployeeSet
4: .Include("Contact")
5: .Where(emp=>emp.Issues>100)
6: .OrderBy(emp=>emp.EmployeeID);
7: }
Notice in line 4, we told EF to include the associated Contact item when we pull each SuperEmployee from the database.
Now in SuperEmployeeMetadata.cs we need to add some metadata to tell RIA Services to include the contact information in data it sends to the client
[Include]
public Contact Contact;
Now, we may want to edit this contact information, so let’s add methods to the DomainService to add new and update our Contact entity.
public void InsertContact(Contact contact)
{
this.Context.AddToContact(contact);
}
public void UpdateContact(Contact currentContact)
{
this.Context.AttachAsModified(currentContact,
ChangeSet.GetOriginal(currentContact));
}
Great. The server side is done, now let’s go to the client.. Here what I want to do is add some UI to the details dataform to show the contact information.
Inside the DataForm in Home.xaml add a button to edit the contact information.
<Button Content="Edit Contact Information..."
Width="205" Height="28"
Margin="15,10,0,0" HorizontalAlignment="Left"
Click="EditContact_Click" >
</Button>
And we need to handle the click event. Here we create a ChildWindow to display a form to view\edit the contact information.
private void EditContact_Click(object sender, RoutedEventArgs e)
{
var emp = dataForm1.CurrentItem as SuperEmployee;
if (emp.Contact == null)
emp.Contact = new Contact();
var w = new EditContactWindow(emp.Contact);
w.Show();
w.Closed += EditContact_Closed;
}
void EditContact_Closed(object sender, EventArgs e)
{
var win = sender as EditContactWindow;
var emp = dataForm1.CurrentItem as SuperEmployee;
if (win.DialogResult == true)
{
emp.Contact = win.Contact;
}
}
Add a new ChildWindow and call it EditContactWindow(). In the codebehind set it up in code behind.
public partial class EditContactWindow : ChildWindow
{
public Contact Contact;
public EditContactWindow(Contact contact)
{
InitializeComponent();
Contact = contact;
this.LayoutRoot.DataContext = Contact;
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
dataform1.CommitEdit();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
}
}
And the Xaml is simply a DataForm to display the results:
<dataFormToolkit:DataForm x:Name="dataform1" CurrentItem="{Binding}">
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<TextBox Text="{Binding ContactName, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding ContactTitle, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Address, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding City, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Region, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding PostalCode, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Phone, Mode=TwoWay}" />
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataFormToolkit:DataForm>
And it looks great!
Notice, back on the server in SuperEmployeeDomainService.metadata.cs, i did give it some metadata to control how the Contact is displayed. This is where I would put validation as well.
[MetadataTypeAttribute(typeof(Contact.ContactMetadata))]
public partial class Contact
{
internal sealed class ContactMetadata
{
// Metadata classes are not meant to be instantiated.
private ContactMetadata()
{
}
[Display(Name="Name")]
public object ContactName;
[Display(Name = "Title")]
public object ContactTitle;
public object Address;
public object City;
[Display(Name = "State")]
public object Region;
[Display(Name = "Zip Code")]
public object PostalCode;
public object Phone;
}
}
We are done!
In this part we looked at how to incrementally evolve a RIA Services + Silverlight application. I showed how to add a new column to an existing table and how to add a whole new table.
Enjoy!
Comments
Anonymous
August 03, 2009
The comment has been removedAnonymous
August 03, 2009
I'd like to see a unit of work with optimistic concurrency. User A get's the data, meanwhile User B alters that same data and commits changes. Then User A commits. I think this is a common scenario in a disconnected application that needs to be handled. Great work - good series :)Anonymous
August 03, 2009
Brad, I see one important step that's missing in two sections of article. When you added a new field (SuperPower) to the SuperEmployee and updated the EDMX file or when you added the contact file to your EDMX file, you said "do a full build and everything should be good", however a build does not add the new field or file to the DomainService that already exist. This is one very important step missing, because how do you get DomainService to generate/add these new entities? I went back & forth and don't see how a build adds those in. Thanks!Anonymous
August 03, 2009
The only way that I've been able to add new entities to my existing DS, is to create a new DS, go through the wizard to create a new entity (with metadata class), then I have to go manually cut the new class from the new DS and manually add it to existing DS and metadata and then delete the newly created DS, and build. Are you saying this feature is now built into RIA? if yes, please tell me how I can use it. Sorry for the new post, couldn't add it to previous one! Thanks!Anonymous
August 03, 2009
>Brad, I see one important step that's missing in two >sections of article. >When you added a new field (SuperPower) to the >SuperEmployee and updated the EDMX file or when >you added the contact file to your EDMX file, you >said "do a full build and everything should be >good", however a build does not add the new field >or file to the DomainService that already exist. This >is one very important step missing, because how do >you get DomainService to generate/add these new >entities? >I went back & forth and don't see how a build adds >those in. Ben -- Thanks for the comment. I think i did handle those in the post, but maybe I was too smooth about it ;-) The new field (SuperPower) does not need to be added to the DomainService in this case.. just updating the entity does the work. The new entity (Contact) does need to be added to the DomainService and I showed that... You can optionally add it in the *.metadata.cs file for things such as validation...i showed that at the very end..Anonymous
August 04, 2009
Great series! Would be very handy to see the use of the Combobox in datagrids, dataforms and child windows. Keep them coming!Anonymous
August 04, 2009
The comment has been removedAnonymous
August 04, 2009
I have a small problem with the TextBlock/AutoCompleteBox! After each key input the Textbox loses focus and the Autocomplete dropdown show for 3 sec. and then dropdown disapear, And the grid is filtered with the string in the textbox?. Thanks!Anonymous
August 05, 2009
The comment has been removedAnonymous
August 06, 2009
@Sanjay The best example I've seen is by Manish Dalal at http://weblogs.asp.net/manishdalal/archive/2009/07/03/silverlight-3-combobox-control.aspxAnonymous
August 06, 2009
I notice the use of [Include] attribute, but imagine we have to filter contacts with a boolean field named 'Active' and we only want to retrieve Active contacts, is there a solution to make this in 1 query ?Anonymous
August 07, 2009
Although this sample includes a parent child relationship the save methods for these entities don't update the relationships in the database. What I'm struggling with is how do I create a new child and hook it up to the parent? I've tried doing something like this in my code: private void OKButton_Click(object sender, RoutedEventArgs e) { var parent = (Parents)(this.newContainerForm.FindNameInContent("ParentComboBox") as ComboBox).SelectedItem; NewChild.Parent = parent; this.newChildForm.CommitEdit(); this.DialogResult = true; } Even if the service saves the child (which for some reason it doesn't do any more), I'm always missing the Parent_Id from the database. Do the RIA Services support Cascading updates when using the [Include] attribute or is this only for single table services ? thanksAnonymous
August 07, 2009
Delordson thanks for the link. But add is not working correctly. I am still struggling to get it work. i have posted the issue in that link also. Thanks, SanjayAnonymous
August 11, 2009
Hello Brad, I've a question. I've already joined 2 tables but I don't know how to join 3 tables. I've Order 1-n OrderDetail and Order 1-n City. I want to select OrderDetail table and CityName from City table where OrderID = '###'. How Linq expression for this. Help me please. Thanks u very much.Anonymous
August 11, 2009
:) I solved my problem Instead of using syntax: include("Order").include("Order.City") I used: include("Order").include("City") That's my wrong.Anonymous
August 13, 2009
Hi Brad, Today I've a question. How do I can select subset of fields from entity in ADO.NET Data Service. I see almost examples here select all fields from entity. Thanks you!Anonymous
August 15, 2009
I also can't seem to get the commitEdit() to work...for the purpose of adding a record to the employee table in the "Walkthrough: Creating your first .NET RIA services Application". Does anyone have a solution?Anonymous
August 19, 2009
I am also not able to get the commitEdit(0 to work. Any help?Anonymous
August 20, 2009
hi Brad nice article, however one issue, I have my DomainService derived from LinqToSqlDomainService and it doesn't have Include method in linq query? Any idea?Anonymous
August 23, 2009
Hi Brad! I love your series, and love the concept of ria services even more. But, I think there is a bug in the sample code (since the first version) , because if you add a new product, it wouldn't appear in the product grid as long as submitting the grid. How could it be corrected? - I have no Idea. Thank you in advance!Anonymous
August 25, 2009
Hello Brad! I didn't get answer to my question, so I try expound it a little deeper. I am not too experienced in silverlight and very new in net ria services. When I try own my own test appl upon your super employee appl, there is three issues in connetion this:
- I have seen, there is no elaboration to refresh the employee datagrid after inserting a superemp (Nothing will be happen with the grid). But if you click the submit button, the newly inserted records will appear in the grid. I have no idea how to enforce refreshing the grid without submitting the data to the server.
- How could be mark the updated entities in the datagrid (for example with an asterix) ? Itt would be very important to show in the tableview wich eelement was channged.
- In the details dataform of the sample, if you make any modification you can cancel it, but after leaving the element (in this case choosing an other employee) you cannnot cancel anymore in spite of that, the dataform marks that the record changed?! Thanks in advance
Anonymous
August 29, 2009
Could you possible create some of the same examples using LinqToSQL instead of Entity framework? Some of my data models don't compile with Entity but work perfectly with LinqToSQLAnonymous
September 09, 2009
The comment has been removedAnonymous
September 22, 2009
Brad, the series is indispensible when trying to work your way through all this stuff. I have done as you suggested above , added new columns to the table, updated the entity model and built the project but the does not update with the new fields. I do have multiple related entities in my model and have also produced a metadata.shared.cs. Does this cause a problem ? Thanks.Anonymous
October 12, 2009
Excellent post... you end the post by adding attributes to the generated metadata.cs class and doesn't discuss how to update the metadata.cs class when the model changes. Here's my update plan in the absence of clever tooling: Make two DomainServices MyDomainService, and MyDomainServiceOrig. When I regenerate the model, I will replace MyDomainServiceOrig (including the metadata.cs). I can then use source control to see what model changes occured between the previous and current metadata.cs. Then I'll manually add the changes to MyDomainService.metadata.cs so as not to disturb the attributes I've manually added there.Anonymous
October 28, 2009
Hi Brad, Can u see the above url posted by you,everthings workins fine for me,but i am not able to add new records and also delete. Even the demo application does not have the features. Please help me out brad i need to do it as it is urgent.. do repl to the post.