Exercise 4: Track and Session HierarchyEvents won’t be very interesting if they have no sessions for attendees to listen to. So our event editing page also needs to provide a list of the tracks in the event, along with the sessions for each track. So we’ll need to add two data grids to our event editor, but we also need to modify the service to ensure that all the necessary entities are available. The domain service operation our Silverlight application currently uses to get event information only returns Event entities. The GetEvents method in the EventManagerDomainService just returns the ObjectContext.Events query, and by default, the Entity Framework does not automatically fetch related entities. (It can perform automatic deferred fetching, but by the time the entities have been returned back to the Silverlight client it’s too late for this to happen because the Entity Framework is no longer in the picture. Its work is already done by then.) For the Home.xaml, this is what we want. Our page shows a list of all the events, so we really don’t want to fetch all related data for all events as it would make the page too slow to load. However, for the individual event editing page, we do need to fetch the related track and session items. Creating a Custom Domain Service Query- Find the GetEvents method in EventManagerDomainService (in the SlEventManager.Web project’s Services folder).
- Make a copy called GetEventsWithTracksAndTalks.
- Modify its implementation as shown below:
public IQueryable<Event> GetEventsWithTracksAndTalks()
{
return this.ObjectContext.Events.Include("EventTracks.Talks");
}
Public Function GetEventsWithTracksAndTalks() As IQueryable(Of [Event])
Return Me.ObjectContext.Events.Include("EventTracks.Talks")
End Function
The Include method tells the Entity Framework which navigation properties we are planning to use on the entities it returns. This causes it to fetch all of the EventTracks and Talks up front for each Event entity returned. - Open EditEvent.xaml in the Silverlight project.
- Add split the main into Grid into two columns.
You can do this by clicking in the blue bar along the top of the design surface to split the UI into two roughly equal-sized columns. - Open the Data Sources panel if it’s not already open.
- Expand the Event entity, and notice that it has an EventTracks item inside it.
- Expand the EventTracks child item, and notice that it in turn contains a Talks item.
These items behave differently than the EventTrack and Talk entities that are directly underneath the EventManagedDomainController element. Entity collections shown as children of another entity collection in the Data Sources window represent specifically those entities that are related to the parent (as opposed to all the entities of that type). If you drag the EventTracks item from inside the Event entity onto the UI, it will produce a DataGrid that shows only the tracks related to the current Event, rather than showing all the EventTrack entities. - Drag the EventTracks item (the one under the Event item) onto the UI.
- Arrange the DataGrid so it is at the bottom left hand side.
- Set the layout to resize as the containing window resizes.
Inspect the XAML, and notice that Visual Studio has done something slightly different with this entity collection. It has not added an extra DomainDataSource but insteadit has added a CollectionViewSource which refers to the existing DomainDataSource, so that it can show items related to the current Event. We’re not yet using the new query operation so there will be no data in the EventTracks DataGrid yet. - In the XAML for EditEvents.xaml, find the DomainDataSource.
- Change the QueryName property to use the GetEventsWithTracksAndTalksQuery to use the new operation you just added.
- Run the application.
- Select the first event and edit it. Be sure to select the first event in the list, as this one has some tracks configured.)
This will fail. The Tracks DataGrid you just added will be empty. You just asked to edit an event that has tracks in, you bound a data grid to the tracks for the selected event, and you used a service operation that asked the Entity Framework to fetch related tracks and talks. Despite all that, nothing appeared. The reason this just failed is that it’s not enough to tell the Entity Framework that we want it to fetch certain related entities. We also need to tell WCF RIA Services that we need those entities to be brought across to the client. By default, it will only bring across those entities that it can see need to be returned based on the signature of the domain service operation. Since our GetEventsWithTracksAndTalks method’s signature only mentions Event entities, that is all we get back.
Allow Related Objects to be Serialized- Open the EventManagerDomainService.metadata.cs(C#) or EventManagerDomainService.metadata.vb(VB) file in the Services folder of the SlEventManager.Web project.
- Find the EventMetadata class.
- Add an [Include](C#)<Include()>(VB) attribute to the EventTracks field.
- Run the application again.
- Select the first event again and edit it. This time you should see some tracks show up in the track list.
You might be wondering why we need to tell both the Entity Framework and WCF RIA Services to include the related entities. There is a good reason for it. WCF RIA Services needs to be conservative, and only expose the information we tell it too, as otherwise it would be hard to control what it made available—it might reveal information we don’t necessarily want revealed. The [Include] attributes need to be there. But we can’t depend entirely on those attributes, because we might want to include different sets of entities in different circumstances. For example, our application now has two domain operations for returning Event entities, one of which returns related tracks and talks, while the other does not. If inclusion of related entities was entirely down to the [Include] attribute we would not have been able to do this. - Go to the Data Sources window.
- Drag the Talks from inside the EventTracks inside the Event onto the bottom right of the UI of EditEvents.xaml.
- Add the [Include] attribute to the EventTrackMetadata class’s Talks field.
When you drag the new Grid on you’ll find an extra DomainDataSource has been added called eventDomainDataSource1. What’s happened here is that Visual Studio has been confused by us changing the QueryName. It makes it think that our XAML no longer contains a suitable DomainDataSource for the Event entities, so it has added another. (And it has also added two new CollectionViewSource items, when we only wanted it to add one.) But this will stop everything working. So you’ll need to delete the new DomainDataSource, and point things (the CollectionViewSource, for example) back at the original DomainDataSource. You will also need to delete the duplicate CollectionViewSource that was created. - Delete the new DomainDataSource1 (see note above).
- Delete the duplicate CollectionViewSource, called eventEventTracksViewSource1. You should now have 2 CollectionViewSource entries.
- Point the CollectionViewSource called eventEventTracksTalksViewSource back at the original CollectionViewSource by modifying the Binding expression’s Source property—make it refer to eventEventTracksViewSource instead of eventEventTracksViewSource1.
<sdk:Page.Resources>
<CollectionViewSource x:Key="eventEventTracksViewSource"
Source="{Binding Path=Data.EventTracks,
ElementName=eventDomainDataSource}" />
<CollectionViewSource x:Key="eventEventTracksTalksViewSource"
Source="{Binding Path=Talks, Source={StaticResource
eventEventTracksViewSource}}" />
</sdk:Page.Resources>
- Run the application again.
- Select the first event. There’s only one talk configured right now, so you’ll only see one listed, and only one the first track is selected.
Implement Adding Tracks and Talks - Add two more buttons to the UI for EditEvents.xaml labeled New Track and New Talk.
- Name the new buttons newTrackButton and newTalkButton respectively.
- Add Click handlers for each and implement them as follows:
private void newTrackButton_Click(object sender, RoutedEventArgs e)
{
Event currentEvent =
eventDomainDataSource.Data.Cast<Event>().Single();
currentEvent.EventTracks.Add(new EventTrack
{ EventTrackTitle = "New Track" });
}
private void newTalkButton_Click(object sender, RoutedEventArgs e)
{
EventTrack track =
eventTracksDataGrid.SelectedItem as EventTrack;
if (track != null)
{
track.Talks.Add(new Talk { TalkTitle = "New Talk" });
}
}
Private Sub newTrackButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim currentEvent As [Event] = EventDomainDataSource.Data.Cast(Of [Event])().Single()
currentEvent.EventTracks.Add(New EventTrack With {.EventTrackTitle = "New Track"})
End Sub
Private Sub newTalkButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim track As EventTrack = TryCast(EventTracksDataGrid.SelectedItem, EventTrack)
If track IsNot Nothing Then
track.Talks.Add(New Talk With {.TalkTitle = "New Talk"})
End If
End Sub
- You will need to add the following using declarations:
using SlEventManager.Web;
using SlEventManager.Web.Services;
Imports SlEventManager.Web
Imports SlEventManager.Web.Services
This code works a little differently than the code to add a new Event. That’s because this time we do want the DomainDataSource to be in on the act: we want the data grids to show the newly added tracks. We are going to let the DomainDataSource remain in charge of the updates. Newly added tracks and talks will get added to the database at the same time as any other changes when the user clicks the Save Changes button added earlier. But there are a couple of other complications caused by the fact that we are not simply adding new items out of the blue. We are adding new items that are related to existing items. For example, the new EventTrack belongs to an Event. So instead of adding it to the domain context’s EventTrack collection, we add it to the current Event object’s EventTracks. (The first line of code in the first handler discovers the current Event. It’s a little eccentric because the DomainDataSource is designed to hold collections of objects, but this particular page has just a single Event. So we need to unwrap it from its collection.) The second handler does something similar, but just grabs the currently selected Track from the track data grid. - Run the application.
- Select an event you created and edit it.
- Click the button to add a new Track. It should appear immediately in the DataGrid. You can use grid to edit the properties of the track.
- Ensure the track is selected.
- Add some talks to the track, and again edit them in the grid.
- Click the Save Changes button.
This will write all the new items to the database. You should see all the ID columns change at this point, because the database-generated IDs get passed back once the data has been inserted. You should find that if you go back to the home page and then go back into your event, the newly added tracks and talks are now visible.
| |