Skip to main content
All docs
V23.2

Execute Business Logic When a Property is Changed

  • 9 minutes to read

In a Controller

Update a Calculated Property When Another Property is Changed

To execute your business logic when a business class property is changed, execute it in the IObjectSpace.ObjectChanged event handler.

The following example illustrates how to update the TotalPrice property of a Sale object when its Count property is changed.

using DevExpress.ExpressApp;

public class MyViewController : ObjectViewController<ObjectView, Sale> {
    protected override void OnActivated() {
        base.OnActivated();
        ObjectSpace.ObjectChanged += ObjectSpace_ObjectChanged;
    }

    private void ObjectSpace_ObjectChanged(object sender, ObjectChangedEventArgs e) {
        if (e.PropertyName == nameof(Sale.Count)) {
            Sale sale = (Sale)e.Object;
            sale.TotalPrice = sale.Count * sale.Price;
        }
    }
    protected override void OnDeactivated() {
        base.OnDeactivated();
        ObjectSpace.ObjectChanged -= ObjectSpace_ObjectChanged;
    }
}

Note

Business objects must implement the INotifyPropertyChanged interface. See this article for details: The Importance of Property Change Notifications for Automatic UI Updates.

For object changes that cannot be tracked via notification mechanisms exposed by the data layer, the IObjectSpace.SetModified method must be called after an object has been changed. This method adds the object passed as the obj parameter to the list of objects to be committed.

Mark a View as Modified

Your business logic can indirectly change a current View object. For example, you can modify a collection property of a business class in code. This won’t enable the Save Action in the business class’ Detail View to commit changes. Call the IObjectSpace.SetModified method and pass the current View object as a parameter.

The following example clears the Tasks collection of the Contact Detail View and marks the Contact object as modified:

using System;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Editors;
using MySolution.Module.BusinessObjects;
//...
namespace MySolution.Module.Controllers {
    public class ClearEmployeeTasksController : ViewController {
        private SimpleAction ClearTasksAction;
        public ClearEmployeeTasksController() {
            ClearTasksAction = new SimpleAction();
            ClearTasksAction.Execute += new SimpleActionExecuteEventHandler(ClearTasksAction_Execute);
        }

        private void ClearTasksAction_Execute(Object sender, SimpleActionExecuteEventArgs e) {
            while(((Employee)View.CurrentObject).Tasks.Count > 0) {
                ((Employee)View.CurrentObject).Tasks.Remove(((Employee)View.CurrentObject).Tasks[0]);
            }
            ObjectSpace.SetModified(View.CurrentObject);
        }
    }
}

Loop Through Modified Objects

It’s possible to traverse through all modified objects in an Object Space to process them in a custom way. The IObjectSpace.ModifiedObjects property returns a collection of modified objects.

The following code shows how to manually save child collection changes made in the parent Detail View:

using DevExpress.ExpressApp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using YourSolutionName.Module.BusinessObjects;

namespace YourSolutionName.Module.Controllers {
    public class MyViewController : ObjectViewController<DetailView, Parent> {
        List<Child> detailObjectsCache = new List<Child>();
        protected override void OnActivated() {
            base.OnActivated();
            ObjectSpace.CustomCommitChanges += ObjectSpace_CustomCommitChanges;
            ObjectSpace.Committing += ObjectSpace_Committing;
            ObjectSpace.Committed += ObjectSpace_Committed;
            ObjectSpace.Reloaded += ObjectSpace_Reloaded;
        }
        protected override void OnDeactivated() {
            ObjectSpace.CustomCommitChanges -= ObjectSpace_CustomCommitChanges;
            ObjectSpace.Committing -= ObjectSpace_Committing;
            ObjectSpace.Committed -= ObjectSpace_Committed;
            ObjectSpace.Reloaded -= ObjectSpace_Reloaded;
            base.OnDeactivated();
        }
        void ObjectSpace_Reloaded(object sender, EventArgs e) {
            detailObjectsCache.Clear();
        }
        void ObjectSpace_Committed(object sender, EventArgs e) {
            detailObjectsCache.Clear();
        }
        void ObjectSpace_Committing(object sender, CancelEventArgs e) {
            IObjectSpace os = (IObjectSpace)sender;
            for (int i = os.ModifiedObjects.Count - 1; i >= 0; i--) {
                object item = os.ModifiedObjects[i];
                if (typeof(Child).IsAssignableFrom(item.GetType())) {
                    detailObjectsCache.Add(item as Child);
                    os.RemoveFromModifiedObjects(item);
                }
            }
        }
        void ObjectSpace_CustomCommitChanges(object sender, HandledEventArgs e) {
            // Implement custom logic to save Detail objects here. 
            foreach (Child detailObject in detailObjectsCache) {
                //...  
            }
        }
    }
}

Track When a View is Changed

If your business logic should be executed after a user starts changing an object or after a user saves changes, handle the IObjectSpace.ModifiedChanged event to catch these moments. For example, you can disable an Action if a user changes an object.

The following code disables an Action when business objects in the current Object Space are changed.

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using System;
// ...
public class ViewController1 : ViewController {
    SimpleAction action1;
    public ViewController1() {
        action1 = new SimpleAction(this, "Action1", DevExpress.Persistent.Base.PredefinedCategory.View);
    }
    protected override void OnActivated() {
        base.OnActivated();
        ObjectSpace.ModifiedChanged += ObjectSpace_ModifiedChanged;
        UpdateActionState();
    }
    void ObjectSpace_ModifiedChanged(object sender, EventArgs e) {
        UpdateActionState();
    }
    protected virtual void UpdateActionState() {
        action1.Enabled["ObjectSpaceIsModified"] = !ObjectSpace.IsModified;
    }
    protected override void OnDeactivated() {
        base.OnDeactivated();
        ObjectSpace.ModifiedChanged -= ObjectSpace_ModifiedChanged;
    }
}

In a Data Model

A common task in business objects is to create a calculated property which value depends on another persistent property. For example, you have the Customer and Order business classes that have a one-to-many relationship between them. The Order business class implements the Price property. In the Customer business class, add a calculated property (Total) that returns the total price of all customer’s orders.

using System.Linq;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using DevExpress.Persistent.BaseImpl.EF;

namespace MySolution.Module.BusinessObjects {
    public class Customer : BaseObject {
        public decimal Total {
            get {
                return Orders.Sum(c => c.Price);
            }
        }

        public virtual IList<Order> Orders { get; set; } = new ObservableCollection<Order>();
    }

    public class Order : BaseObject { 
        public virtual decimal Price { get; set; }
        public virtual Customer Customer { get; set; }
    }
}

// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.

This code calculates the Total property value when a Customer object is loaded. To notify bound editors that the Total property value changed, subscribe to changes of the Orders collection and send notifications about the Total property value changes when they occur:

using System.ComponentModel.DataAnnotations;
using System.Collections.ObjectModel;
// ...
public class Customer : BaseObject, INotifyPropertyChanged {
    // ...
    ObservableCollection<Order> orders;
    public virtual IList<Order> Orders { get => orders; }

    public Customer() {
        orders = new ObservableCollection<Order>();
        orders.CollectionChanged += (s, e) => OnPropertyChanged(nameof(Total));
    }

    #region INotifyPropertyChanged implementation

    PropertyChangedEventHandler propertyChanged;
    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
    string customerName;
    public virtual string CustomerName {
        get { return customerName; }
        set {
            customerName = value;
            OnPropertyChanged();
        }
    }    
}

//Note that the Customer class implements the INotifyPropertyChanged interface explicitly. You need to send notifications about changes to all class properties explicitly - UseChangeTrackingProxies does not work in this case.

You can also notify that the Total property value is changed from the Order business class. Refer to the following article that describes this: How to: Calculate a Property Value Based on Values from a Detail Collection.

See Also