ZeroSharp

Robert Anderson's ones and zeros

Better Handling of Decimals in the XAF Audit Trail

| Comments

The following screenshot shows the detail view of an object change from the DevExpress XAF Audit Trail. The DecimalValue property was changed from 123.45 to 543.22.

Why is the OldValue property is displayed with two trailing zeros? The corresponding property is defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[DefaultClassOptions]
public class MyClass : XPObject
{
    public MyClass(Session session)
        : base(session)
    { }

    //...

    private decimal _DecimalValue;
    [ModelDefault("DisplayFormat", "{0:n2}")]
    public decimal DecimalValue
    {
        get
        {
            return _DecimalValue;
        }
        set
        {
            SetPropertyValue("DecimalValue", ref _DecimalValue, value);
        }
    }

    private XPCollection<AuditDataItemPersistent> _ChangeHistory;
    public XPCollection<AuditDataItemPersistent> ChangeHistory
    {
        get
        {
            if (_ChangeHistory == null)
            {
                _ChangeHistory = AuditedObjectWeakReference.GetAuditTrail(Session, this);
            }
            return _ChangeHistory;
        }
    }
}

Explanation and fix

A C# decimal is a type which represents a number’s value and its precision. It actually stores the number of trailing zeros along with the value. For the NewValue, it has stored the decimal value as the user entered it - with no trailing zeros. Howevever, for the OldValue, it has retrieved the value from the database and used the SQL column definition to determine the precision.

The default SQL column type that XPO column type for properties of type decimal is the money type (see the MSDN documentation) which stores 4 decimal places of precision. If we override this with, say, a DECIMAL(28, 13), the audit trail would show 13 decimal places of precision.

From the user’s perspective, this looks a little confusing, so let’s fix it.

During the initialization of your application (in Application_Start for a web application), add an event to the AuditTrailService as follows.

AuditTrailConfig.Initialize();

and then declare the AuditTrailConfig helper class as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static class AuditTrailConfig
{
    public static void Initialize()
    {
        AuditTrailService.Instance.SaveAuditTrailData += Instance_SaveAuditTrailData;
    }

    static void Instance_SaveAuditTrailData(object sender, SaveAuditTrailDataEventArgs e)
    {
        NormalizeOldValuesDecimalPrecision(e);
    }

    private static void NormalizeOldValuesDecimalPrecision(SaveAuditTrailDataEventArgs e)
    {
        var decimalAuditTrailDataItems = e.AuditTrailDataItems
                                          .Where(i => i.OldValue is decimal);

        foreach (AuditDataItem auditTrailItem in decimalAuditTrailDataItems)
        {
            // remove any trailing zeros from OldValue
            auditTrailItem.OldValue = ((decimal)auditTrailItem.OldValue).Normalize();
        }
    }
}

The Normalize() method is an extension method. See the trick in my last post for more information.

1
2
3
4
5
6
7
public static class DecimalExtensions
{
    public static decimal Normalize(this decimal value)
    {
        return value / 1.000000000000000000000000000000000m;
    }
}

And then the same change would be logged as follows.

Comments