How to delete a label file in AX 2012

If you end up with an obsolete label file in AX 2012 and want to delete it you cannot just right click and select delete from the popup menu.

To get rid of the label file you can do the following:

  1. Create a new model. In this example named DeleteMe
  2. Move your label file to the DeleteMe model
  3. Use AXUTILs delete function to delete DeleteMe
  4. Restart the AOS

You have successfully removed the label file from your installation.

Advertisements

Manipulating data while ignoring a TTSABORT

OK, the subject of this post will probably be categorised as dangerous and by some as borderline stupid. The content should NOT be considered used in everyday work since it very easily could give you massive inconsistency in data and a broken system. There! I said it and I do not want to be the one saying I told you so afterwards. This blog post is only for information and not a recommendation.

So what is all the fuzz about? Well, sometimes it could be nice allowing some data to be inserted, updated or deleted although a TTSABORT command or an error is thrown. That could be relevant in regards of a log being updated no matter what the result of a job is or the likes.

The mechanism is known from the batch queue that has its status updated no matter how the job finishes.

 

The trick is using the UnitOfWork and UserConnection framework within the TTS-scope. This allows you to create a connection to the database that is not a part of the TTS but is running its own show.

And this is where it gets dangerous/stupid in some scenarios. Imagine a inventory transactions customisation manipulating data in some circumstances within the TTS and some without. The result could be data almost impossible to recover to a consistent state again.

 

In this example we want to update the Tax Group Id on the Customer groups and log the changes to a table no matter what happens.

I have created a table – DEMO_Log – with CustGroup as the only field. We would like this table to receive a new record not depending on success nor failure in the update of the CustGroup table.

Next step is to create a class doing the work. In this case it is called DemoIgnoreTTSAbort and it has a run method like this:

private void run()
{
    info("BEFORE UPDATING");
    this.showInfo();
    
    ttsBegin;
    this.updateCustGroup();
    ttsAbort;

    info("AFTER NORMAL UPDATE");
    this.showInfo();
    
    ttsBegin;
    this.updateCustGroup2();
    ttsAbort;

    info("AFTER ALTERNATIVE UPDATE");
    this.showInfo();
}

It starts by showing the current records in a resume like this

private void showInfo()
{
    CustGroup custGroup;
    DEMO_Log log;

    select count(RecId) from custGroup
        where custGroup.TaxGroupId;

    select count(RecId) from log;

    info(strFmt("Customer groups with Tax group id: %1", custGroup.RecId));
    info(strFmt("Log records: %1", log.RecId));
}

The idea is to give us a count of customer groups with Tax group ids and the count of records in our log table.

Then we – inside a TTS scope – uses the updateCustGroup method to try updating the groups like this:

private void updateCustGroup()
{
    CustGroup custGroup;

    while select forUpdate custGroup
        where ! custGroup.TaxGroupId
    {
        custGroup.TaxGroupId = 'TX';
        custGroup.update();
        this.insertInLog(custGroup.CustGroup);
    }
}

Each CustGroup record with no TaxGroupId content is updated with ‘TX’ and a log is inserted using the method insertInLog that goes like this:

private void insertInLog(CustGroupId _custGroupId)
{
    DEMO_Log log;
 
    Log.CustGroup = _custGroupId;
    log.insert();
}

I think that one is pretty self explaining…

The TTS scope is then ended with an TTSABORT so no records within the scope is updated/inserted.

We then pull the showInfo once more to se if anything has happened. And nothing has. No surprise.

 

The next part is a new TTS scope and a method (updateCustGruop2) which is almost identical to updateCustGroup is used:

private void updateCustGroup2()
{
    CustGroup custGroup;
  
    while select forUpdate custGroup
        where ! custGroup.TaxGroupId
    {
        custGroup.TaxGroupId = 'TX';
        custGroup.update();

        this.insertInLog2(custGroup.CustGroup);
    }
}

 

The only difference between updateCustGroup and updateCustGroup2 is that it calls insertInLog2 instead of insertInLog after updating each record.

This method is the key to all this and it looks like this:

private void insertInLog2(CustGroupId _custGroupId)
{
    DEMO_Log log;
    UnitofWork unitOfWork;
    UserConnection userConnection;

    userConnection = new UserConnection();
    unitOfWork = new UnitofWork();

    log.CustGroup = _custGroupId;
    log.insert();

    unitOfWork.insertonSaveChanges(log);
    unitOfWork.saveChanges(userConnection);
}

Compared to insertInLog it starts by adding to new variables – unitOfWork and userConnection which gives us an extra connection to the database not included in the TTS scope. First is a basic instantiation followed by the insert of the DEMO_Log record like in the insertInLog method. The next statement is where we tell unitOfWork to insert the log record(s) upon the call of the saveChanges method. There is a deleteOnSaveChanges and updateOnSaveChanges if you want something else than inserts.

Finally we call the saveChanges using the above declared userConnection.

Ending the run method with another call of the showInfo reveals that although we abort our TTS scope and the customer groups remain unchanged the log is fully updated:

Message (05:09:54 am)
BEFORE UPDATING
Customer groups with Tax group id: 0
Log records: 0
AFTER NORMAL UPDATE
Customer groups with Tax group id: 0
Log records: 0
AFTER ALTERNATIVE UPDATE
Customer groups with Tax group id: 0
Log records: 7

Once again: Remember that this is only to be used with extreme caution …