Search This Blog

Showing posts with label audit. Show all posts
Showing posts with label audit. Show all posts

Monday, June 26, 2017

AuditTrail - How to show the change history for the current object in a separate form instead of a nested ListView

I wanted to draw your attention to the scenario and solution in our Support Center, which were found helpful by at least 4 other XAF customers using XPO for data access. As you know, by default, we offer our users a solution based on the nested ListView, which is automatically generated when you declare a collection property like this one:

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


The solution I am promoting allows you to keep the layout less complex and invoke this rarely used or quite advanced feature from a small PopupWindowShowAction showing a ListView:


It will be interesting to hear about your preferences in this regard. Please let me know in comments. Thanks!


Thursday, July 23, 2015

Making the AuditTrail functionality operate correctly with several XPObjectSpaceProviders when storing data in separate databases (15.1.6)

I wanted to inform the community of a solution we have just finished testing for the next XAF version in response to the following customer's business scenario:

"i followed this instruction to work with two databases in one application. so far so good. 
i also managed to include the security module and get it working, but now i don't know how to use the "audit trail" module! it doesn't seem to matter in which project i include it, but it will not save anything to the database."


As you probably know, XAF supports connecting to several databases and even using both Entity Framework (EF) and eXpress Persistent Objects (XPO) at the same time (examples: onetwothree), although it is not the primary scenario, to be honest. 

In short, if you were in need of such a configuration and wanted to have AuditTrail capabilities, it would "just work" starting with v15.1.6. If you feel confident to check technical details on this, see my description in the T263982 - AuditTrail - Ensure support for scenarios with several XPObjectSpaceProviders ticket.
By providing support for this module, we continue working in the direction we took in the past, which I also discussed in the past blog. As always, our team is looking forward to hearing your feedback in this regard.

Tuesday, February 18, 2014

AuditTrail module performance improvement in v13.1.10 and v13.2.7

I would like to draw your attention to an improvement we recently made in the AuditTrail module:

AuditTrail - Low performance when saving data under certain circumstances

Please install the version 13.1.10+ or 13.2.7+ and let us know whether it now works better in your app.

I am focusing on this due to a recent discussion in this support ticket. In short, if something is not performing well or fast enough, it is always worth researching the cause of this behavior, rather than disabling a feature completely, especially when it relates to built-in XAF modules.

There may be some tricky or uncovered scenarios, and specialized profiling tools (e.g., AQTime and SQL Server Profiler) may help to find the cause of such performance issues. If you are not so familiar with using profilers (imho, it is still important to master them for the future for any professional developer, though) to profile the project yourself, feel free to send it to us, so that we can research it and find the best solution. I understand that preparing an entire project or test sample may take some time, but the result is worth it. This may also help improve the product itself, and the experiences that other users may be facing, which is always appreciated by the community. Thanks!

Thursday, March 21, 2013

Avoiding data redundancy for the 'last modified' date when using the Audit Trail module

Recently, our old (he is actually quite young - just has been using XAF for many years;-)) XAF customer, Nate, posted an interesting question at http://www.devexpress.com/issue=Q482829:

"Well, on my BO I want to replace four persistent fields UpdatedOn, UpdatedBy, CreatedOn, CreatedBy with non-persistent fields that pull this information from AuditTrail since it's already there and available. These values are to be shown on detail views so at a glance the user can get this info without going into the audit trail tab. I'm duplicating data by storing these values myself. However the concern is that audittrail is so large that querying this data will be too taxing on performance".

Indeed, how to avoid that duplication? While there is no unambiguous answer on whether this change will be worth from the performance point of view (it is not possible to predict whether the performance difference will be noticeable or not, because everything depends on the amount of history data, existence of indices and other factors), here is a solution to his requirement:

        [PersistentAlias("[<AuditDataItemPersistent>][^.Oid=AuditedObject.GuidId].Max(ModifiedOn)")]
        public DateTime ModifiedOn {
            get {
                return Convert.ToDateTime(EvaluateAlias("ModifiedOn"));
            }
        }

For testing purpuses I put this code into the Contact class in our MainDemo app.I find this solution interesting, because the criterion I used is quite tricky. Here I am using a cool XPO feature called Free Joins, because my Contact class has no direct relation to the audit data (yes, there is a ChangeHistory property in it, but it is not association).

Take special note that I am using ^. operator to refer to the Oid property of my Contact class (see here for more details on this syntax).

The AuditedObject is a property of the built-in AuditDataItemPersistent class used to store audit data. This property is of the AuditedObjectWeakReference type (a descendant of the XPO's XPWeakReference class) and has a few properties: GuidId, IntId and a string TargetKey one, inheriting from the base class. Take special note that my criterion assumes that the Contact's Oid key property is of the Guid type. So, if your business object has an integer key, then you would use the IntId property instead of the GuidId one in the criterion.

There is also a way to avoid this dependency on the key's type:

        [PersistentAlias("[<AuditDataItemPersistent>][Contains(AuditedObject.TargetKey, ToStr(^.Oid))].Max(ModifiedOn)")]
        public DateTime ModifiedOn {
            get {
                return Convert.ToDateTime(EvaluateAlias("ModifiedOn"));
            }
        }

Note that I had to use the Contais criteria function because the TargetKey is a string in a special format:

Screenshot


UPDATE:
I have just found that our another customer, Kim already tried using this approach in his application and found out that the performance is not that good with lots of records:
http://www.devexpress.com/issue=Q416995
I personally think that this is exactly the case when "code beauty" and avoiding de-normalization are not really worth it. Hopefully, these observations will be helpful for other users.