Monday, 19 January 2009

Restore(ctrl-F5) and access control (AllowEdit) problem


 
Trivia:
It is usual programming pattern in AX that one overrides active() method on the datasource to allow/disallow data entry on fields depending on values of any fields in record.
It is also usual pattern to do similar thing overriding modified() method on datasource fields.

I would like to warn anyone using that pattern , that every screen in AX2009 SP0  will fail to behave as expected.
 
Problem:
When you re-read the contents of the record on the form using F5 function key, AX 2009 SP0 does call the datasource's active method, allowing you to re-set access (   _DS.object().allowEdit(...)    )
but DOES NOT call *_DS.active()  method when one press Ctrl-F5.
 
Scenario of failure:
User arrives at the record, enters some data , is allowed access to the other fields (based on previous input).
Now user presses Ctrl-F5, and all field values return to initially fetched, but *_DS.active() method is not called, potentially making editable fields which were not  available upon arrival to that record.   User happily changes the data, and developer ask himself a question how did it happened.
 
BTW: _DS.cursorNotify() is not called either.
 
It would be good if someone would confirm this behaviour in AX4 and AX2009SP1
 
Solution:
For the time being there's small generic solution to this. You need to modify the SysSetupFormRun.task() to make it call active() after Ctrl-F5 was pressed.
 
public int task(int _p1)
{
    #task
    FormDataSource formDataSource;
 
    int ret;
 
    if (_p1 == #taskFilter)
    {
        formDataSource = this.objectSet();
        if (formDataSource &&
            formDataSource.queryRun() &&
            formDataSource.queryRun().args() &&
            !formDataSource.queryRun().args().caller())
        {
            formDataSource.queryRun().args().caller(this);
        }
    }
 
    ret = super(_p1);
    //mku------------------------------------------------- to call the datasource's active and allow re-setting the access----------
    if(_p1 == 2839)
    {
        formDataSource = this.objectSet();
        if(formDataSource)
        {
            formDataSource.active();
        }
    }
 
    //mku ------------------------------------------------------  end  ------------------------------------------------------------
    return ret;
}



Tuesday, 13 November 2007

New approach to Exceptions and Transactions handling

As much as I like the transactional support in the core of the X++ language, as much I dislike the connection between exception handling and transactions.

In my opinion transaction should be part of the syntax and have syntax based scope.

Here is my suggestion to Microsoft:

1) ban the standalone ttsbegin and ttscommit

2) replace it with special try-catch syntax , as drafted below:

try(new Connection())

{ //implicit TTSbegin

[do your stuff]

} //implicit TTScommit

catch(Exception::Error)

{ //implicit TTSabort.

[oops]

}

Both Oracle and MSSQL2005 support the nested transactions, and that would be ideal opportunity to use it.

conn = new Connection();

try(conn)

{ //implicit TTSbegin = BEGIN TRANS LEVEL1

try(conn)

{ //implicit TTSbegin = BEGIN TRANS LEVEL2

SalesFormLetterInvoice::post(SalesTable);

} //implicit TTScommit = COMMIT TRANS LEVEL2

catch(Exception::Error)

{ //implicit TTSabort = ROLLBACK TRANS LEVEL2

if(..cannot compensate the error...)

throw Error(....)

else

[compensate the error.]

}

} //implicit TTScommit = COMMIT TRANS LEVEL1

catch(Exception::Error)

{ //implicit TTSabort = ROLLBACK TRANS LEVEL1

[...]

}

That way each try-catch block is eligible to work, not only this started outside of transaction, and when needed each of them can operate own (sub) transaction.

It is also much less demanding on the developer, preventing improper or unbracketed use of ttsbegin and ttscommit. Normal (non db) try-catch bloack work as usually, allowing to catch X++ and CLR exceptions without unwanted rollbacks.

Thursday, 16 August 2007

Coordinated transactions using MSDTC

It would be very nice to be able to take advantage of distributed transactions.
I saw a lot of intefaces written for AX and all of them were unnecessarily complicated to make sure no double processing takes place nor nothing is lost in the middle. All that complication can be avoided if we make use of Microsoft Distributed Transaction Coodinator. Easy said, but unfortunatelly Axapta does not provide such functionality 'out of box'. But not everything is lost, and with some luck it seems to be possible.
MSDN says: In the first step application must call DtcGetTransactionManager from xoleHlp.dll. Piece of cake.
Short script with DLL and DLLFunction and... Oops. "Called dll returned wrong value in BP register". A first obstacle is here: DtcGetTransactionManager is compiled with __cdecl directive, but AX calls it as it were __stdcall, thus stack is not cleaned, and BP mismatched.
To pass this problem I wrote a xolehlp.dll wrapper, which has functions declared in the way AX has no problem to call it.
Now I can call DtcGetTransactionManager and get the object with IID_TransactionDispenser interface. I can even start distributed transaction now.
[to be continued]

Tuesday, 7 August 2007

Real world processing: TTS and exceptions

Hi everyone,

This is my first post in my blog, so I will start with something which bugs me right from the start of my work with Axapta.
What I find the major flaw in the X++ design is the fact that when exception is thrown, the main, outermost transaction is automatically rolled back, and there is no support for nested transactions. Automatic rollback is even advertised as a 'feature'.

That simple thing is preventing code in AX from doing the real world mass processing, as any exception that will be generated (from the code you have no control over) will destroy all work done so far, even if in particular situation your code is perfectly capable of handling the problem. In fact it should be your, designer's decision about the scale of the problem. Your code has to decide about scale of the problem: it's local, and there's special path to restore from that error- let's go with backup scenario. Outgrows foreseen options? Let's escalate.

Auto-rollback executed as a part of exception is simply taking that decision away from your code and that substantially cripples the number of ways processing can be designed.

Next time I will try to highlight possible solutions to that fundamental issue.
Please do not hesitate to scribble me a comment with your view.