3.4 Using a multi-value property for a simple sub-object
Multi-value properties on a details screen are usually modeled within relational database design as simple child tables, which have a reference to the parent object and a single column for the values. The list of sales reasons on a “sales order” object is a very good example of such a design. Let's have a look at the following screenshot.

The list of sales reasons has the main column with the reason ID, and an audit column with the modified date. What we want to have on this screen for editing sales reasons is a convenient multi-select control showing reason names instead of the current table. We also want it on a separate child panel on this screen grouped with other sales-related fields, such as the salesperson and sales territory, also highlighted in yellow.
We will start by removing any elements related to the “reason” sub-object, that were added for us by the Model CRUD generator, such as the operations, data objects and views, as shown below.

The easiest way to also clean any previously generated artifacts for those removed elements is to rebuild the model, which runs a model Clean command followed by a Build command. This will delete those generated files, and will remove them from their projects.
Before you do anything like that though, you want to absolutely make sure that any generated classes containing custom code will be preserved during the Clean. If you previously set preserve-on-clean attribute to true on the svc:customize element for the “sales order” object, as we showed before, then you don't need to do anything now. Otherwise, you can remove some lines in the top comments in your customized files, as described in those comments. It is also a good idea to check everything in your version control before you run the Clean or Rebuild commands on the model.
After we rebuild the model, we will want to define a dynamic enumeration for the “sales reason” object by running the “Enumeration Read List” generator on the sales_reason.xom file, and trim the output of the generated “read list” operation to keep just the “reason id” and “name”, as follows.

In addition, we can override the Blazor and WebForms controls that are used for multiple selection on the “sales reason” type. We will use a PickList control that has two lists and Add/Remove buttons to move items from one list to another. For Blazor this is implemented by the XPickList component from the Xomega.Framework.Blazor package, and for WebForms we can use the user control uc:PickListControl (notice the xmlns:uc namespace at the top) provided with the Xomega solution template. The following picture illustrates this setup.

Next, we'll follow the technique for grouping fields in a child panel that we described earlier. We will declare a new data object in the model with a class SalesOrderSalesObject, and then a new structure "sales info", whose parameters will be added to that data object, as shown below.

Notice that we marked the sales reason parameter with the list attribute set to true to make it a multi-value property. Since we have dynamic enumerations for all of these fields, which can decode IDs to names, we don't need to read anything else in addition to these IDs, so we can use the same structure for both “read” and “update” operations.
On the SalesOrderSalesObject data object we can specify proper labels for the territory and the salesperson, set the customize attribute to true, in order to set up cascading selection for them, and mark the territory as a trigger to enable AutoPostBack for WebForms.

To finish up the model updates, we will add the SalesOrderSalesObject as a child of the SalesOrderObject under the name sales.

And then we will replace territory and sales person parameters in the output of the “read” operation, and input of both “create” and “update” operations, with a reference to the "sales info" structure using the same name.

After that we will build the model, and then add the following method to the extended SalesOrderService class, which populates the SalesInfo structure for the given sales order.

We will also add another method that updates the specified sales order with the provided SalesInfo structure as follows.

Notice how the method adds or removes sales reasons to the child list based on the supplied new list of reasons.
After that we can use these methods in the placeholders for the corresponding parameters in the generated service implementation class. The Read method will be updated as follows.

And both Create and Update methods will be updated like this.

To set up the cascading selection of the territory and the salesperson, we will add the following code to the customized subclass of the SalesOrderSalesObject class in the Client.Common project.

Let's run the application again, and review our changes.

As you see, instead of the Reason tab, our sales order details screen now has a Sales tab, where our sales info fields are displayed. The list of salespersons cascades off of the selected territory, and the Sales Reason field has a convenient selection control to pick one or more reasons for the sales order.
Next: 3.5 Showing fields from related objects >