Skip to main content
.NET 8.0+

Implement a Custom Base Persistent Class

  • 6 minutes to read

XAF ships with the Business Class Library that contains a number of persistent classes ready for use in your applications. These classes are derived from the BaseObject base persistent class declared in the same library. We recommend that you use this feature-rich persistent class. Inherit your XPO classes from it to use features in your data model. Refer to the following topic for the list of supported features: Base Persistent Classes.

Extend the BaseObject Class

Create a BaseObject descendant and apply the NonPersistent attribute to it to implement additional functionality in a base class:

using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Xpo;
//...
[NonPersistent]
public abstract class BasePersistentObject : BaseObject {
    public BasePersistentObject(Session session) : base(session) { }
    public override void AfterConstruction() {
        base.AfterConstruction();
        CreatedOn = DateTime.Now;
    }
    protected override void OnSaving() {
        base.OnSaving();
        UpdatedOn = DateTime.Now;
    }
    private DateTime _createdOn;
    private DateTime _updatedOn;
    [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")]
    public DateTime CreatedOn {
        get { return _createdOn; }
        set { SetPropertyValue("CreatedOn", ref _createdOn, value); }
    }
    [ModelDefault("AllowEdit", "False"), ModelDefault("DisplayFormat", "G")]
    public DateTime UpdatedOn {
        get { return _updatedOn; }
        set {
            SetPropertyValue("UpdatedOn", ref _updatedOn, value);
        }
    }
}

Inherit your data model classes from the base class to extend them with the CreatedOn and UpdatedOn properties. The NonPersistent attribute specifies that XPO should not use a separate table for the BasePersistentObject class, and the declared properties should be stored in tables of its descendants. Refer to the following topic for more information: Inheritance Mapping.

For instructions on how to store user-specific information, refer to the following article: How to implement the CreatedBy, CreatedOn and UpdatedBy, UpdatedOn properties in a business class.

You may need to alter or disable some features shipped with the BaseObject class. Use one of the base persistent classes offered by XPO to do this: XPO Classes Comparison. XPO also allows you to implement a base class from scratch, although this solution is rarely required and is intended for a few advanced scenarios: Add Persistence to an Existing Hierarchy: Change the Base Inheritance.

Implement the Auto-Generated Primary Key Property

If the base class you derive from does not have an auto-generated key property, you need to implement it manually. We do not recommend implementing composite or compound keys for new databases. While it is possible to design persistent classes for legacy databases with composite keys in certain scenarios, it is always better to modify the database schema to avoid this as using composite keys imposes some limitations on the default functionality. Refer to the How to create a persistent object for a database table with a compound key KB article to learn more. The following code snippet illustrates a GUID property marked with the Key attribute specifying that XPO should auto-generate its values. Note that XPO supports automatic generation of key property values via the Key attribute for the Int32 and GUID types only.

using System;
using System.ComponentModel;
using DevExpress.Xpo;
using DevExpress.Xpo.Metadata;
using DevExpress.ExpressApp;
//...
[NonPersistent]
public abstract class BasePersistentObject : XPCustomObject {
    public BasePersistentObject(Session session) : base(session) { }
    [Persistent("Oid"), Key(true), Browsable(false), MemberDesignTimeVisibility(false)]
    private Guid _Oid = Guid.Empty;
    [PersistentAlias(nameof(_Oid)), Browsable(false)]
    public Guid Oid { get { return _Oid; } }
    protected override void OnSaving() {
        base.OnSaving();
        if (!(Session is NestedUnitOfWork) && Session.IsNewObject(this))
            _Oid = XpoDefault.NewGuid();
    }
}

It does not make much sense to persist this class as it contains a single property (for the auto-generated primary key) and thus the class is marked as non-persistent. As a result, primary key columns will be created in database tables corresponding to a descendant of this class. This eliminates redundant JOIN statements from SQL queries generated by XPO, thus improving database performance. Additionally, you should override the OnSaving method in the demonstrated way to support the correct saving of new objects derived from your class and created via a nested unit of work in specific situations.

Implement the ToString Method

To complete custom base persistent class implementation, override its ToString method to manage default properties. Default properties are displayed in Lookup Property Editors and take part in form caption generation. Additionally they are automatically used by the FullTextSearch Action and are displayed first in List Views. When implementing a custom base persistent class, override the ToString method to check whether or not the DefaultProperty attribute is applied to the class and return its value.

[NonPersistent]
public abstract class BasePersistentObject : XPCustomObject {
    //...
    private bool isDefaultPropertyAttributeInit;
    private XPMemberInfo defaultPropertyMemberInfo;    
    public override string ToString() {
        if (!isDefaultPropertyAttributeInit) {
            DefaultPropertyAttribute attrib = XafTypesInfo.Instance.FindTypeInfo(
                GetType()).FindAttribute<DefaultPropertyAttribute>();
            if (attrib != null)
                defaultPropertyMemberInfo = ClassInfo.FindMember(attrib.Name);
            isDefaultPropertyAttributeInit = true;
        }
        if (defaultPropertyMemberInfo != null) {
            object obj = defaultPropertyMemberInfo.GetValue(this);
            if (obj != null)
                return obj.ToString();
        }
        return base.ToString();
    }
}