Skip to main content
.NET 8.0+

Optimistic Concurrency Control

  • 3 minutes to read

The optimistic concurrency control (OCC) mechanism is used in XAF applications if you inherit from any of the base persistent classes (except for the XPLiteObject). This topic describes how data conflicts are handled in an XAF application.

Object-Level Locking

Example: Two users open the same object on different workstations and make changes. The first user saves their changes to the database. When the second user tries to save their changes afterward, a message appears indicating a conflict:

In WinForms applications:

ConcurrencyWin

In ASP.NET Web Forms applications:

ConcurrencyWeb

After refreshing the object (if required), the second user will be able to make modifications against the renewed object and persist them. If the first user deleted the object, the second user will not be able to make any modifications, and will instead receive the following message.

ConcurrencyDeleted

Field-Level Locking

In multi-user applications, two users might edit the same record simultaneously. If they change different fields (for instance, one edits the description, the other adds a file), XAF can merge their changes. To support this, set the static XpoDefault.TrackPropertiesModifications property to true.

  • In the Program.cs file of a WinForms application project:

    static void Main() {
        DevExpress.Xpo.XpoDefault.TrackPropertiesModifications = true;
        // ...
    }
    
  • In the Global.asax.cs file of an ASP.NET Web Forms application project:

    protected void Session_Start(object sender, EventArgs e) {
        DevExpress.Xpo.XpoDefault.TrackPropertiesModifications = true;
        // ...
    }
    

When this property is set to true and a user modifies an object that was already changed by another user, a dialog window that suggests merging changes is displayed.

  • In WinForms applications:

    ConcurrencyMerge

  • In ASP.NET Web Forms applications:

    ConcurrencyMerge

Merging is possible when users modify the values of different fields. If both users change the same field value, merging is not possible, and the user can either refresh data or cancel saving.

  • In WinForms applications:

    ConcurrencyRefresh

  • In ASP.NET Web Forms applications:

    ConcurrencyRefresh

The dialogs illustrated above are provided by the ProcessDataLockingInfoController Controller from the SystemModule module.

Note

To see field-level locking in action, refer to the Concurrent Modifications section of Feature Center demo installed with XAF. By default, the Feature Center demo is installed in %PUBLIC%\Documents\DevExpress Demos 25.1\Components\XAF\FeatureCenter.NETFramework.XPO.

View-Level Locking

Besides concurrency control at the data level, the LockController from the SystemWindowsForms module tracks changes at the View level (in WinForms applications). It displays the following message when an object is being modified in two or more different Views.

ConcurrencyLock

Customize Collision Behavior Dynamically at Runtime

XAF includes a ProcessDataLockingInfoController. Use this controller to implement your own collision handling strategies. Handle the DataLockingProcessing event to manage information about modified records and collisions.

The following code snippet enables automatic merge in XAF applications:

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.SystemModule;

namespace MainDemo.Module.Controllers;

public class AutoMergeViewController : ViewController {
    private ProcessDataLockingInfoController lockController;

    private void OnDataLocking(object sender, DataLockingProcessingEventArgs e) {
        foreach (var info in e.DataLockingInfo.ObjectLockingInfo) {
            info.CanAutoMerge = info.CanMerge;
        }
    }

    protected override void OnActivated() {
        base.OnActivated();
        lockController = Frame.GetController<ProcessDataLockingInfoController>();
        lockController.DataLockingProcessing += OnDataLocking;
    }

    protected override void OnDeactivated() {
        base.OnDeactivated();
        if (lockController != null) {
            lockController.DataLockingProcessing -= OnDataLocking;
            lockController = null;
        }
    }
}