Silverlight 3 ComboBox Control
This post outlines technique for displaying ComboBox control with .net RIA services to handle Foreign Key scenarios.
When ComboBox control was introduced as part of Silverlight 2, I blogged about a technique to use ComboBox control in foreign key scenarios, to workaround the lack of SelectedValue\SelectedValuePath property. I further enhanced the technique to handle cascading selection as outlined in this post. Fast forward to Silverlight 3. ComboBox control still does not have SelectedValue\SelectedValuePath property and the same technique still works. Moreover, if you are using Silverlight 3 with .net RIA services, majority of the code is automatically generated by the framework. In fact, with Silverlight 3 and .net RIA services there is no requirement for SelectedValuePath property!
Background
In business applications with relation database as backend data store, foreign keys are often used to form association between entities. When working with such an entity in UI, a friendly description is shown to user, while system handles the actual foreign key value behind the scene. For ComboBox, this translates to displaying foreign key entity with a friendly description (perhaps using a custom template), and storing the results of selection as foreign key value to the base entity. However, ComboBox does not provide a SelectedValuePath property to set selected foreign key value back to the base entity. Simplest workaround is to use SelectedItem property. By adding Foreign key entity as a property to the base entity, we can data bind SelectedItem to the foreign key entity, automating the whole process.
With .net RIA services, you can do this easily, just by setting Include attribute on the associated foreign key entity. This results in foreign key entity being added a property to the base entity, which can then be data bound to SelectedItem property. Now when SelectedItem sets new foreign key entity, property setter in turn sets related foreign key value property, as indicated by the Association attribute. All of this code is already in place, generated automatically by the .net RIA services code generator. All you have to do is to data bind to SelectedItem and take care of ItemsSource.
Model
I will use the same Address/City model that I had previously used for Silverlight 2 ComboBox post, so that you can contrast the code, except that this time I will start with a database model. Model consists of Address and City tables.
Application
Create a Silverlight Navigation Application. Next add the ADO.NET Entity Data Model as AddressModel and generate the AddressEntities from database tables, Address and City.
Next add a Domain Service Class as AddressService, adding Address and City entities. Select Enable editing and also generate associated meta data.
In the class AddressMetadata, decorate City field with Include attribute.
internal sealed class AddressMetadata {
// Metadata classes are not meant to be instantiated.
private AddressMetadata() {
}
public Guid AddressId;
[Include]
public City City;
...
This will ensure that City entity in included as a property in the Address class and it is sent to client. Also modify GetAddress method in AddressService class to include City in retrieved data.
public IQueryable<Address> GetAddress() {
return this.Context.Address.Include("City");
}
Compile and build the application.
Auto generated City Property
Find the Generated Code folder (unhide by clicking Show All Code for client application) and navigate to City field in Address entity class. Note that City property is decorated with Association attribute that ties Address.CityId foreign key to City.CityId primary key. Also note the Set property assessor for City property. When ever a new City value is set, it also automatically sets CityId property to proper foreign key value. Following is the auto generated code for the City Property:
[Association("City_Address", "CityId", "CityId", IsForeignKey = true)]
[XmlIgnore()]
public City City {
get {
if ((this._city == null)) {
this._city = new EntityRef<City>(this, "City", this.FilterCity);
}
return this._city.Entity;
}
set {
City previous = this.City;
if ((previous != value)) {
this.ValidateProperty("City", value);
if ((previous != null)) {
this._city.Entity = null;
previous.Address.Remove(this);
}
this._city.Entity = value;
if ((value != null)) {
value.Address.Add(this);
this.CityId = value.CityId;
} else {
this.CityId = default(Guid);
}
this.RaisePropertyChanged("City");
}
}
}
So far, .net RIA services has generated all the code necessary for City property and tying it to corresponding CityId foreign key property. All that is now needed for us to do is to add code to allow ComboBox to find a City entity in the ItemsSource entity list.
Shared City Partial Class
Add shared City partial class as City.shared.cs to the Silverlight server web application.
public partial class City {
public override bool Equals(object obj) {
if (null == obj) return false;
City cityToCompare = obj as City;
if (null == cityToCompare) return false;
return CityId.Equals(cityToCompare.CityId);
}
public override int GetHashCode() {
return CityId.GetHashCode();
}
}
This code will be copied over to client and is used to find City entity from list of cities.
City List Provider
Next add class CityListProvider to the Silverlight client Application.
public class CityListProvider {
AddressContext _dc;
public AddressContext DomainContext {
set {
_dc = value;
_dc.Load<City>(_dc.GetCityQuery());
}
}
public EntityList<City> CityList {
get {
return _dc.Cities;
}
}
}
ComboBox XAML
Add a new page to display Address data using DataGrid. Following shows City column XAML snippet:
<data:DataGridTemplateColumn Header="City">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding City.CityName}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding CityList, Source={StaticResource cityListProvider}}"
SelectedItem="{Binding City, Mode=TwoWay}"
DisplayMemberPath="CityName" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
Note that ComboBox control’s SelectedItem is directly bound to the City property. User is shown friendly “CityName” by setting DisplayMemberPath. Also add CityListProvider as a resource to the root Grid control.
Modify code behind to set DataGrid ItemsSource to Address property on AddressContext (domain context) and load addresses using LoadAddress method of address domain context.
AddressContext _dc;
void AddressList_Loaded(object sender, RoutedEventArgs e) {
_dc = new AddressContext();
addressDataGird.ItemsSource = _dc.Addresses;
//
CityListProvider cityListProvider = LayoutRoot.Resources["cityListProvider"] as CityListProvider;
cityListProvider.DomainContext = _dc;
//
_dc.Load<Address>(_dc.GetAddressQuery());
}
Compile and test the application. Note that appropriate City field is automatically selected when ComboBox drop down opens, thanks to Equality override in City class. When a new City is selected from the drop down, CityId is auto updated to reflect the new selection.
Use of ComboBox for Implementing foreign key scenarios is now completely automatic with aid of .net RIA service. Framework generates necessary Foreign Key entity property and also generates corresponding code to keep underlying foreign key value in sync.
Steps to get started
- Use Include attribute on foreign key entity. Also include foreign key entity in data retrieval.
- Provide Equality override code to find entity in entity list
- Data bind ItemsSource to entity list provider
- Data bind SelectedItem directly to foreign key entity
Source Code: SL3ComboBox.zip (560KB) - (Updated Sep-21-2009)
Technorati Tags:
Silverlight