Manish Dalal's blog

Exploring .net!

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.

Modelimage

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.

Applicationimage

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());
}

imageCompile 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
  1. Use Include attribute on foreign key entity. Also include foreign key entity in data retrieval.
  2. Provide Equality override code to find entity in entity list
  3. Data bind ItemsSource to entity list provider
  4. Data bind SelectedItem directly to foreign key entity

Source Code: SL3ComboBox.zip (560KB) - (Updated Sep-21-2009)

Technorati Tags:

Comments

Thosebug said:

how smart is RIA services or comobox or both to get the foreign key values?

let's thinkg in something about Employee/Address and Address has Locality (country/city/state..) the combobox will have a thousands of records right?

what about performance? I think combobox is not usable for this scenario, at least not for business app. I prefer AutocompleteBox.

anyway in other hand the problem goes to RIA services...because requires to serialize before go to the client, the a thousand of localities would be serialized as xml/json/etc....this is not good...back to asp.net/html world ?

# July 6, 2009 9:30 AM

Fan said:

Great!! does it work in DataForm?

# July 9, 2009 1:47 PM

Marc said:

Nice post.

I wonder, will the Silverlight 3 version of ComboBox actually be a "combo" box, rather than a dropdown menu? i.e. users will be able to type into it. The current name is misleading!

I also wonder whether the size of the dropdown will change when you add/remove items after the control has finished loading. Lets hope so!

# July 10, 2009 11:44 AM

Fan said:

Hi Manish,

I try to use DataPager control because of lots of records in the table. the comboBox can not work properly!! (error: the entity does not support 'Add' operation ??)

# July 16, 2009 3:42 PM

Marlon said:

Hi Manish,

How do you go about on using this on DataForm? Your guidance will be greatly appreciated.

Cheers

# July 18, 2009 10:13 AM

Nic Oughton said:

Awesome - that works brilliantly. Thanks!

# July 20, 2009 6:35 AM

manish.dalal said:

@Fan,

It seems to works fine with DataPager, I have updated source code to include paged example

@Marlon,

Please download updated source code to see DataForm usage

# July 21, 2009 1:18 PM

Fan said:

It Works perfect. Thanks Manish, Great job!!

# July 22, 2009 12:07 PM

Elina said:

Great works

Thank you very much

# July 30, 2009 4:08 PM

manish.dalal said:

Fan,

You need to make sure that you are using the same instance of domain context as on the main form. Set domain context on child window similar to City provider class.

# July 30, 2009 4:51 PM

Tim said:

If tried your appliction, and it seems to work. Only the AddressForm.xaml does not save the city object and AddressFormDDS.xaml does not select the right city.

is this only, or is this problem common.

# July 31, 2009 9:43 AM

Fan said:

Hi Manish,

In order to avoid lot of problems from using childwindow, I used another way not solved the problem. Anyway,Thank your help very much. Maybe it will be useful in the future(binding lookup table in the childwindow). Thanks again!!  

# July 31, 2009 2:05 PM

Mark Johnston said:

Excellent post and code sample! This was a HUGE help for a "traditional" .NET developer to better understand the workings of Silverlight/XAML.

Thanks so much!

# August 1, 2009 3:04 PM

Dave said:

Super Great article!! Manish!

this solution helped us so much!!!!

but I have the same issue as Tim (Friday, July 31, 2009 9:43 AM by Tim)

The selecteditem in AddressFormDDS.xaml is not correct. I really would like to use DomainDataSource, but I do not know how to set the selectedItem to the correct value. In your example it will populate the combobox, but the selecteditem is incorrect.

Tim is also correct about the saving problem in AddressForm.xaml, but I added one line of code to get that working:

void saveButton_Click(object sender, RoutedEventArgs e)

{

    // add this to get the saving to work.

    addressDataForm.CommitEdit();

    _dc.SubmitChanges();

}

I really would appreciate if someone knows how to get the correct selecteditem working in AddressFormDDS.xaml

Thanx in advance!

# August 3, 2009 10:32 AM

Sanjay said:

I just started with SL3 & RIA services. Based on this post, I am trying to insert new record using DataForm & ComboBox.

In my test application, there are two entities HardwareAsset & LocationMaster.

LocationMaster contains master list of location & LocationID is primary key (identity column) which is foreign key in HardwareAsset table.

When I insert new hardware asset using DataForm & ComboBox, record gets inserted correctly in HardwareAsset but for every record it creates new row in LocationMaster table.

In domain service, even if I comment insert operation of LocationMaster, still record is added in LocationMaster table.

     public void InsertLocationMaster(LocationMaster locationMaster)

       {

           //this.Context.AddToLocationMaster(locationMaster);

       }

How to fix this issue? Any help is appreciated.

Thanks,

Sanjay

# August 3, 2009 1:49 PM

Nathan said:

I am also experiencing the same issue as Sanjay (Using add in combobox in a dataform). Using this technique it adds entries into both the foreign key table and the primary key table. I would greatly appreciate your help in understanding why this is happening.

# August 3, 2009 2:38 PM

Tommy said:

I did exactly as this article explained and my datagrid didn't get the ProgramName as I needed. It's empty. Seems like the ProgramListprovider("cityListprovider") doesn't get the list from the program table.

Any idea what happen? I checked the association was fine.

# August 29, 2009 5:59 PM

Tommy said:

Additional, I tried the following:

<riaControls:DomainDataSource x:Name="programDataSource"

                                                             QueryName="GetProgramsQuery"

                                                             AutoLoad="True"

                                                             >

                                   <riaControls:DomainDataSource.DomainContext>

                                       <web:OleDataDomainContext/>

                                   </riaControls:DomainDataSource.DomainContext>

                               </riaControls:DomainDataSource>

                               <ComboBox ItemsSource="{Binding Data, ElementName=programDataSource}"

                                     SelectedItem="{Binding Program, Mode=TwoWay}"

                                     DisplayMemberPath="ProgramName" />

                               <ComboBox ItemsSource="{Binding ProgrmList, Source={StaticResource programListProvider}}"

                                     SelectedItem="{Binding Program, Mode=TwoWay}"

                                     DisplayMemberPath="ProgramName" />

One combobox with dds and one with ProgramListProvider.

The one with dds working just fine. It can shows ProgramName, but the other just show nothing. Please help.

Thank you.

# August 29, 2009 6:14 PM

Maca007 said:

I've found similar problem to others.  That is, the "address form dds" page does not correctly select the right combo box item.  In addition to this, I have found that it only happens for the first 2 items in the DataForm list.  I re-ordered my list of addresses and it always was the first 2 DataForm items that were incorrect.

I have posted a possible bug onto the Silverlight forum, so it will be interesting to see what other people have found.

silverlight.net/.../280036.aspx

# September 1, 2009 7:32 PM

Evertom Scopim said:

I´m going trough the same combobox selected item problems.... if u have any kind of solution for this problem let-me know! sierrote@uol.com.br

# September 16, 2009 3:54 PM

manish.dalal said:

@Evertom Scopim, Maca007, Sanjay, Nathan

I have update source code to fix issue with address form dds. It now selects proper city.

# September 21, 2009 1:07 PM

Evertom Scopim said:

Thanks for the update!

I´ve just download and took a look in the changes....

I think this way only the cities that already have any relationship to the adress table will be display in the combobox items. Well i´ll take a look and rebuild your example database and have some practice with it.

# September 22, 2009 3:57 PM

Evertom Scopim said:

Ok! I´ve done the tests and now i understandd why you load the cities context even "without binding" it!

Really thnx!

# September 22, 2009 4:14 PM

TOUMI Fethi said:

Hi,

I see your example : great work.

I want to use in my application a static class in wich i will have many list that will be loded with data in application start up.

  public static class BusinessManager2

   {

       public static IQueryable<Web.DataAccess.TDDESPCP> TDDESPCPCache { get; set; }

   }

Then, i want to use these list as itemsource for (combo, grid, ..) in xaml.

I see in the web an example where these list are dictionary and with converter they can do it.

My lists are not dictionary and i prefer use them as static ressource.

Is there any way.

In your example i want the CityListProvider become static and in application startup i load the city list. is it possible ?

# September 30, 2009 4:21 PM

Jacky Kenhjiro said:

Silverlight 3  With .net RIA services,Domain Service

Test in VS2008 Run Data and Form Show OK

but Deploy To WEB (use VS2008 Publish) Show From no Data ...

Please Help mee...  

# October 19, 2009 8:04 AM

Bathery said:

Got it...once i set the same instance of the DomainContext my issue got resolved.  Now having a slight performance issue..:)

# October 30, 2009 2:57 PM

PentaDragon said:

This seems an extraordinary amount of manual intervention for a pattern that is practically universal in every LOB application in existence. :-(

# November 12, 2009 5:16 AM

Hugo said:

Great article. But then I just asked myself a (dumb?) question: why not using domaindatasource for both the grid and the combobox column?

I tried to use something similar to what is in AddressFormDDS.xaml (source code) for the grid and did not work. See below:

       <riacontrols:DomainDataSource x:Name="TransactionDataSource" QueryName="GetTransactionsQuery" AutoLoad="True">

           <riacontrols:DomainDataSource.DomainContext>

               <ds:CashflowContext/>

           </riacontrols:DomainDataSource.DomainContext>

       </riacontrols:DomainDataSource>

       <riacontrols:DomainDataSource x:Name="CategoryDataSource" QueryName="GetCategoriesQuery" AutoLoad="True">

           <riacontrols:DomainDataSource.DomainContext>

               <ds:CashflowContext/>

           </riacontrols:DomainDataSource.DomainContext>

       </riacontrols:DomainDataSource>

       <data:DataGrid x:Name="TransactionListing" AutoGenerateColumns="False" ItemsSource="{Binding ElementName=TransactionDataSource, Path=Data}">

           <data:DataGrid.Columns>

               <data:DataGridTextColumn Header="Description" Binding="{Binding description}"></data:DataGridTextColumn>

               <data:DataGridTemplateColumn Header="Category">

                   <data:DataGridTemplateColumn.CellTemplate>

                       <DataTemplate>

                           <TextBlock Text="{Binding category.description}" />

                       </DataTemplate>

                   </data:DataGridTemplateColumn.CellTemplate>

                   <data:DataGridTemplateColumn.CellEditingTemplate>

                       <DataTemplate>

                           <ComboBox ItemsSource="{Binding ElementName=CategoryDataSource, Path=Data}"

                                     SelectedItem="{Binding category, Mode=TwoWay}"

                                     DisplayMemberPath="description" />

                       </DataTemplate>

                   </data:DataGridTemplateColumn.CellEditingTemplate>

               </data:DataGridTemplateColumn>

           </data:DataGrid.Columns>

       </data:DataGrid>

I will appreciate any input. Manish?

# November 30, 2009 10:05 PM

Sunlightnh said:

I can't download the Source Code: SL3ComboBox.zip (560KB) - (Updated Sep-21-2009)

# December 17, 2009 9:11 PM

Pouya said:

Thnaks so much,i appriciated

# April 30, 2010 8:19 PM