Doens.be

  • Linkedin flickr twitter delicious Facebook Youtube

Posts Tagged ‘X++’

Records with RecId 101090

I was using the postload method of the HRMBenefitType-table to fill the description-field with language-sensitive description (from LanguageTxt). This works fine when I view the record, but not when I use a lookup.

I’ve put a break-point on my postload method and noticed the recId (this.recid of the HRMBenefitType table). This is always the 101090. When I check this in the tablebrowser, this does not match the actual RecId valule.
I checked the SQL-trace-log and noticed the following instruction:

SELECT A.HRMBENEFITTYPEID,A.DESCRIPTION,A.RECVERSION,101090 FROM HRMBENEFITTYPE A WHERE (DATAAREAID=?) ORDER BY A.DATAAREAID,A.HRMBENEFITTYPEID OPTION(FAST 1)

The solution to this problem is simple. Just change the CacheLookup property to EntireTable and the strange behaviour of the RecId-field stops.

Dialog Extended

One of my colleagues (Koen Dedecker) was looking for a way to prevent user interaction with other forms while a certain dialog is shown. After some research he found the solution on the blog Dynamics AX tools and tutorials from Vanya Kashperuk. A few years ago (2007) he made a simple extension of the dialog class that makes this possible. You can find more information and download some code samples on http://kashperuk.blogspot.com/2007/06/3-dialog-extensions.html.

The trick is actually simple:

  • Create a new variable of the type Boolean en the classDeclaration of the Dialog-class
  • Create a public parm method for this new variable to get/set the value
  • Extend the wait-method by placing the following code before the close-statement:
    dialogform.formRun().wait(this.parmShowModal());

Now when you make a new instance of the dialog class just set the value of the parmShowModal() to true. From now on this dialog will stay on top.

A few things you should know about temporary tables

How do you set a table temporary:
  • You can set the property ‘temporary’ in the AOT to yes –> this table is always temporary
  • You can declare a buffer of a table and call the method setTemp() or setTempData(), from that moment on the buffer contains temporary data.
    CustTable custTable;
    ;
    custTable.setTmp();
    if (custTable.isTmp())
    {
        // Do something with your temporary table
    }
Things to know:
  • When you declare a buffer of a record of temporary table type, the table does not contain any values.
  • Memmory and filespace aren’t allocated for a temporary table till the first record is inserted. This means that you have to watch out for client/server problems.
    When the first record is inserted in a buffer on the client tier, the memory is allocated there. All actions (insert / update / delete) to this buffer will run trough that tier, so try to reduce round-trips and improve your performance.
  • When you declare 2 different buffers of the same temporary table, they will both have a life of their own. To share the date between the tables use the setTmpData method.
    for example: tmpCommonBuffer1.setTmpData(tmpCommonBuffer2);
  • You can’t (normally) set up logging on temporary tables.
  • Use the method isTmp() to know if a record is temporary or not (true –> temporary, false –> phisical)

My opinion: Temporary tables are very useful and can help you to easily manipulate data you don’t need store permanently, but watch out where and how you use them.

The Ax Infolog

Wel all know the small dialog that gives the user usefull information about what is happening in Ax. In this post I will tell u some more about this.

You can add information to the Infolog by calling:

  • Infolog.add(…)
  • info(…)
  • warning(…) or checkfailed(…)
  • error(…)
  • You can add some structure in it by using setPrefix(…)
  • Using the Proxy-class for the Enterprise Portal


(more…)

Execute a job on data in several companies – part2

In one of my previous posts I explained that you can switch company with the keyword changecompany. I also made a remark that you should set the value of your table-value to null after each changecompany.

Small code example (bad):

1
2
3
4
5
6
7
8
9
10
11
12
DataArea dataArea;
PurchTable purchTable;
;
while select dataArea
where !dataArea.isVirtual
{
    changecompany(dataArea.id)
    {
        select firstonly purchTable;
        info(strfmt("%1 (%2)", purchTable.purchId, purchTable.dataAreaId));
    }
}

The result is:

You can clearly see that the table variable is is known in the company you declared it.

Small code example (good):

1
2
3
4
5
6
7
8
9
10
11
12
13
DataArea dataArea;
PurchTable purchTable;
;
while select dataArea
where !dataArea.isVirtual
{
    changecompany(dataArea.id)
    {
        purchTable = null;
        select firstonly purchTable;
        info(strfmt("%1 (%2)", purchTable.purchId, purchTable.dataAreaId));
    }
}

The result:

So by making this small code change it runs perfectly.

NOTE: When you are calling a new method in the changecompany statement, you don’t have to set all the common variables that you declare  equal to ‘null’. This because they are declared after the companychange.

Crosscompany

Yesterday I talked about executing code/getting records in several companies. Now the changecompany is not the only method. You can also get data out of several companies by using the keyword crosscompany in your select statement.

Example:

1
2
3
4
5
6
7
8
9
Address     address;
container   conCompanies = [ 'cee', 'dat' ];
;
while select
crossCompany :conCompanies address
order by dataAreaId
{
    //Do something
}

Execute a job on data in several companies

There is a verry simple trick to loop through some companies and execute some code in them.

Just write:

DataArea dataArea;
;
while select dataArea
where !dataArea.isVirtual
{
    changecompany(dataArea.id)
    {
        // Do Something...
    }
}

Note! Make sure that you clear the table variable after each companychange (common = null;).

Batch job performance boost

Did you ever have trouble with the performance of your batch-job? Well maybe this small trick can shorten the time your batch runs.

The big idea: Try to split up your gigantic batch in smaller pieces. Launch a batchjob that produces many smaller batchjobs (tasks) that can handle a subset of the data you need to process. For example you can create a batch that creates a batch with sub-tasks for each company.

How do you plan a batch from code?

1
2
3
4
5
6
7
8
9
10
11
12
BatchHeader batHeader;
BatchInfo batInfo;
TstRunBase rbbTask;
;
rbbTask = TstMyBatch::construct();
batInfo = rbbTask.batchInfo();
batInfo.parmCaption("MyBatch");
batInfo.parmGroupId("");
batHeader = BatchHeader::construct();
batHeader.addTask(rbbTask);
batHeader.save();
info(batInfo.parmCaption());

You can download a quick example I made. This is a job you can schedule and it will produce a new Batch job with a task for each company when you don’t select a company while shedualing (Class_TstRunBase.xpo). I hope this can help you.

Schedule the batch:

The result:

Tip for overriding methods

There is a simple and generic way to force overriding a method you created. To do so you just need create your new method and place a ‘throw error’ statement in it. To finish you can add the static method missingOverride from the error class and the funcName() to your error. Now when this method is called or the super() in you child class is called, you will get a error that you need to override your method.

public identifiername myMethod()
{
    throw  error(Error::missingOverride(funcName()));
}

When you use this peace of code you will get the following error:

Note: The error-class also has some other usefull static methods like: missingFormActiveBuffer, missingOverload, missingOverride, missingParameter, missingRecord and my favorite wrongUseOfFunction

Expressions in query ranges – limitations

When you want to construct a query-object that contains OR-conditions, you can construct something like this:

1
2
3
4
5
6
queryBuildRange.value(strFmt('((%1 == %2) || ((%1 == %3) && (%4 == "%5")))',
                    fieldStr(InventTable, ItemType),
                    any2int(ItemType::Service),
                    any2int(ItemType::Item),
                    fieldStr(InventTable, ProjCategoryId),
                    queryValue("Spares")));

(you can find more info on Axaptapedia: Expressions in query ranges)

Now there is a limitation to this way of working. When the generated string in the queryBuildRange.value is longer than 250 characters (the length of the range EDT), Ax cuts the generated string off when you open the ‘modify query’ dialog. I have tried to enlarging the EDT, but is not a good solution. The error does not occure when you open the ‘modify query’ dialog, but it occures when you close it.
A small (but limited) work-around could be to make the name of the datasources on the form/report where you want to apply this trick smaller. I hope to find a better solution, but this does the trick for now.