Search This Blog

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.

Friday, March 15, 2013

Split View (aka MasterDetailMode=ListViewAndDetailView) improvements

We have implemented the following feature request in version 13.1:
ListView - Allow placing the master ListView on right or bottom sides when MasterDetailMode = ListViewAndDetailView
Technically, we have extended the ModelListViewSplitLayout interface by a new ViewsOrder property that will allow you specify the display order of the ListView and its DetailView
Refer to the attached screenshots for more details:
Horizontal_detailviewlistview

Vertical_detailviewlistview

I hope you like this improvement.

Beware of Session.DataLayer in middle-tier scenarios

I would like to pay your attention to a couple of Support Center tickets on the subject: one and two.
It is important to remember that when you use DataServerObjectSpaceProvider with a dedicated application server (as per Middle Tier Security - .NET Remoting Service), then you cannot write the code like this:

        Session session = ((XPObjectSpace)View.ObjectSpace).Session;
        UnitOfWork uow = new UnitOfWork(session.DataLayer);


I recommend you use the View.ObjectSpace object and its methods to query and manipulate your data, if possible. This is because it will always work correctly regardless of whether you are using a middle-tier server or not.


If, for some reason, you cannot follow this recommended approach, use the Session.ObjectLayer property instead of the Session.DataLayer one.

BTW, you can test the Session.DataLayer property to execute your business logic on the server or on the client only:

protected override void OnSaving() {
if(Session.DataLayer != null && !(Session.ObjectLayer is DevExpress.ExpressApp.Security.ClientServer.SecuredSessionObjectLayer)
) {
        // Server
}
else { 
        // Client
}
base.OnSaving();
}

Here are also screenshots that show values for these properties when the OnSaving is called on the server or on the client sides:



Since we touched OnSaving here, I would also remind you that it can be executed several times (3 times in the screenshot above for the middle-tier scenario) in a general case (as per XPO Best Practices), so it is probably not the best place for your logic at all, unless you are ready to introduce additional checks here.


It is technically possible to run business logic on the application server side via the IApplicationDataService.Commiting event. I am afraid we currently do not have much documentation on this feature as we are improving it to support more scenarios (audit, validation, etc.).



See Also: