Customize Business Object's Metadata

This topic describes how to alter an existing XPO or Entity Framework (EF) business model without modifying its code. In the Model Editor, end-users (application administrators) can add custom fields to business objects without recompiling their application. XAF application developers can also add custom fields to objects implemented in external libraries. Custom fields can be calculated (an expression used to compute the field value should be provided), or persistent (a new column is added to a database table automatically).

Custom fields declared in the Application Model are automatically collected and registered within the Types Info subsystem. Pay attention at the following peculiarities of the runtime custom fields usage.

  • Generally, persistent fields should be added at design time only. It is not good practice to allow end-users to alter the database schema - allow this only as a last resort measure.
  • It is strongly recommended to restart the application after a custom field is deleted at runtime.
  • Custom fields added to the user differences (Model.User.xafml file) are not supported when the ThreadSafeDataLayer Data Access Layer is in use. In XAF, this Data Layer is enabled when you instantiate an XPObjectSpaceProvider using the constructor that takes the threadSafe parameter and set this parameter to true. The SecuredObjectSpaceProvider used in Integrated Mode of the Security System uses the ThreadSafeDataLayer by default, when the threadSafe parameter is not specified. As a workaround, copy a custom field to the administrator's differences (Model.xafml) by using the Model Merge feature.
  • Calculated fields are always evaluated on the client side. So, sorting, grouping, filtering and summary calculation do not work for calculated columns in Server and InstantFeedback modes. If you use XPO and create a new calculated field by one of the approaches described in this topic, the PersistentAliasAttribute is automatically added to this newly created field, thus, these operations are available for the corresponding column.
  • You cannot add custom persistent fields to the EF data model.
  • You cannot add non-calculated fields to non-persistent classes.

Note that if you change a Mobile Application Model, you need to rebuild an application.

This topic includes the following sections:

Note

To see custom fields in action, refer to the Custom Fields section in the Feature Center demo installed with XAF. The Feature Center demo is installed in %PUBLIC%\Documents\DevExpress Demos 18.1\Components\eXpressApp Framework\FeatureCenter by default. The ASP.NET version of this demo is available online at http://demos.devexpress.com/XAF/FeatureCenter/.

Add a Custom Calculated Field in the Model Editor

To add a custom calculated field, invoke the Model Editor at design time or in a WinForms application at runtime, or use the standalone Model Editor tool (e.g., to add a field to an ASP.NET application). Then follow the steps below.

  1. Right-click the BOModel | <Class> | OwnMembers node and choose Add... | Member.

    AddCustomField_ME

  2. Focus the new member node. In the property grid to the right, specify the IModelMember.Name and IModelMember.Type values.
  3. Pass the expression to be used to compute the field value to the IModelMember.Expression property. You can click the ellipsis button located to the right of the property value to invoke the Expression Editor dialog. In this editor, you can select functions, operators and operands using editor controls.
  4. If you are creating a custom field at runtime, specify the IModelCommonMemberViewItem.PropertyEditorType value. Choose an appropriate Property Editor from the combo box. At design time, the PropertyEditorType value is optional - an appropriate Property Editor is determined automatically. Note that platform-specific Property Editors are not available in the common module.
  5. Save your changes and restart the Model Editor.
  6. Create a Detail View item or List View column mapped to the newly added custom field (see Add an Editor to a Detail View and Change Field Layout and Visibility in a List View).

The image below illustrates the member node settings described above.

AddCustomField_ME2

Add a Custom Persistent Field in the Model Editor in Visual Studio

Creating a custom persistent field is very similar to creating the calculated field described in the previous section. The only difference is that you should set the IModelMember.IsCalculated property to false instead of specifying the Expression value.

Note

The following combination of features is not supported when used together.

In this configuration, your application loads information on custom persistent fields from the database and then updates the database schema. However, a thread-safe data layer does not support altering the data model after the database connection is established.

Add a Custom Persistent Field in the Model Editor at Runtime

By default, the creation of custom persistent fields is allowed at design time only (the IsCalculated value is not editable). To allow end-users to add custom persistent fields in the Model Editor at runtime, set the ModelMemberReadOnlyCalculator.AllowPersistentCustomProperties static property to true. The created field should then be merged to the application-level model differences (see Model Merge Tool). To allow updating of the database schema after a field is added at runtime, set the XafApplication.DatabaseUpdateMode property to DatabaseUpdateMode.UpdateDatabaseAlways. Also, consider the note about ThreadSafeDataLayer from the previous section. A column mapped to the current field will automatically be added to the database table.

Important

Add Custom Fields in Code

To extend an existing business class from an external class library, you can add a custom field in code. Edit the Module.cs (Module.vb) file and override the ModuleBase.CustomizeTypesInfo method. The code below demonstrates how to add a persistent field (PersistentField1), a persistent field with the ImmediatePostDataAttribute applied (PersistentField2) and a calculated field (CalculatedField).

using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
// ...
public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    base.CustomizeTypesInfo(typesInfo);
    TypeInfo personTypeInfo = typesInfo.FindTypeInfo(typeof(Person)) as TypeInfo;
    if (personTypeInfo != null) {
        personTypeInfo.CreateMember("PersistentField1", typeof(int));
        personTypeInfo.CreateMember("PersistentField2", typeof(int)).AddAttribute(new ImmediatePostDataAttribute());
        personTypeInfo.CreateMember("CalculatedField", typeof(int), "PersistentField1 + PersistentField2");
    }
}

To add a custom member to a Domain Component, handle the XafApplication.SettingUp event instead of overriding the CustomizeTypesInfo method.

Access a Custom Field in Code

The following snippet illustrates how to access a custom field in a Controller context using the IMemberInfo.GetValue method.

Person person = this.View.CurrentObject as Person;
ITypeInfo personInfo = this.Application.TypesInfo.FindTypeInfo(typeof(Person));
int? value = personInfo.FindMember("CalculatedField").GetValue(person) as int?;

The following snippet illustrates how to access a custom field in a Controller context from the PropertyEditor.ControlValue property.

ViewItem viewItem = ((DetailView)View).FindItem("CalculatedField");
decimal? value = ((PropertyEditor)viewItem).ControlValue as decimal?;

Create Associations in Code

The following snippet illustrates how to declare an association between two class' properties when you have no access to these classes code and cannot apply the AssociationAttribute directly.

public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    base.CustomizeTypesInfo(typesInfo);
    ITypeInfo typeInfo1 = typesInfo.FindTypeInfo(typeof(DomainObject1));
    ITypeInfo typeInfo2 = typesInfo.FindTypeInfo(typeof(DomainObject2));
    IMemberInfo memberInfo1 = typeInfo1.FindMember("Object2");
    IMemberInfo memberInfo2 = typeInfo2.FindMember("Object1s");
    if(memberInfo1 == null) {
        memberInfo1 = typeInfo1.CreateMember("Object2", typeof(DomainObject2));
        memberInfo1.AddAttribute(new DevExpress.Xpo.AssociationAttribute("A", typeof(DomainObject2)), true);
    }
    if(memberInfo2 == null) {
        memberInfo2 = typeInfo2.CreateMember("Object1s", typeof(XPCollection<DomainObject1>));
        memberInfo2.AddAttribute(new DevExpress.Xpo.AssociationAttribute("A", typeof(DomainObject1)), true);
        memberInfo2.AddAttribute(new DevExpress.Xpo.AggregatedAttribute(), true);
    }
    ((XafMemberInfo)memberInfo1).Refresh();
    ((XafMemberInfo)memberInfo2).Refresh();
}

As you can see in the code above, the IMemberInfo.AddAttribute method's second parameter is true. In this case, Types Info does not reload information from XPDictionary immediately After adding all required attributes, call the XafMemberInfo.Refresh method for each updated member. Alternatively, you can call XafTypesInfo.RefreshInfo for each type containing updated members:

public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
    // ...
    typesInfo.RefreshInfo(typeof(DomainObject1));
    typesInfo.RefreshInfo(typeof(DomainObject2));
}
Tip

To access the XPDictionary directly, use the XpoTypesInfoHelper.GetXpoTypeInfoSource method.

See Also