Tuesday, September 29, 2009

Syntax error in Product Builder module

Recently we came across a small but annoying problem in the product builder modules.
Our product model, perfectly working for quite some time, refused to compile after a minor modification. The error message read:

Compilation error: *** Error: -1, Syntax error.

The error popped up when we compiled a code treenode from the product model. We checked our code, everything looked OK. We didn't make a typing error.

One of the product builder variables we used was named 'DDB'. No problem so far, but when we tried to use this variable in the code segment of our treenode, the error came up. Why?
When used in the code treenode, Ax will declare a variable in the class it creates for your product model, with the same name as used in the product model.
Only, DDB is a reserved name, as this is the name of a function in Ax (it calculates the accelerated depreciation of an asset). Variables in classes cannot be named after built-in functions.

So our solution: rename variable DDB to something else.
Problem solved, something learned.

Friday, September 25, 2009

How to print a report straight to the printer (and not to screen)

When a user runs a report, it's a good idea to first print it to screen. When the outcome is not what was expected, the user can change his criteria or parameters and run the report again. When the report meets the expectations, a hardcopy can be printed. Save paper, think green!

But sometimes, you'll want a report to go straight to the printer, without a user intervention. For example when you run a report by code (in a production environment, an automatic generated loading list, ...).

In these scenario's, you'll want to adjust the printer settings.

1. Set the desired printer
Add following code in the init method of your report

element.deviceName(yourprinternamegoeshere);

Make sure a printer by that name exists on the PC where the report runs.

2. Don't print to screen, print straight to printer
Add following code in the init method of your report

element.setTarget(PrintMedium::Printer);

This will make the report go straight to the printer (instead of the screen).
You can add additional settings, like the number of copies as well.
Like this:

element.printJobSettings().copies(2);

For automation purposes, the above code examples may be useful to run reports.

How to run a report by code

After last post, how to open a form by code, we gonna do the same thing for reports.
First the short version

new MenuFunction(MenuItemOutPutStr(Cust),MenuItemType::Output).run();

The above code will start the Cust report (a list with the basic information for a selection of customers).
If you want some information passed to your report, you can use the args class.
Like this

static void RunReportByCodeA()
{ Args args = new Args();
;
args.parm('yourparmhere');
new MenuFunction(MenuItemOutPutStr(Cust),MenuItemType::Output).run(args);
}


Just like in the previous post, even more control over your report may be required. Then you can use the long version.

static void RunReportByCodeB()
{ ReportRun reportRun;
Args args = new Args(reportstr('Cust'));
;

// args.caller(this);
// args.record(parmRecord);
// args.parm(parmStr);

reportRun = new Reportrun(args);
reportRun.init();
reportRun.run();
}


The above version is exactly like the short version we saw earlier. But it shows where we are going with this.

static void RunReportByCodeB()
{ Object reportRun;
Args args = new Args(reportstr('Cust'));
;
reportRun = new Reportrun(args);
reportRun.init();
reportRun.yourmethodgoeshere();
reportRun.run();
}

We changed the type of reportRun to Object.
Now we have even more control over the report, as we can execute any method defined on it. Use it to provide extra information to the report (use a specific design depending on data, print straight to printer instead of screen, ...).

Again, there is one drawback: While programming, your method doesn't show up in the IntelliSense, showing all the available methods. So be carefull of typo's. (They don't give a compile error, but they will give you a run-time error.)

Wednesday, September 23, 2009

How to open a form by code

A short tutorial on how to open a form in Dynamics Ax (Axapta) by code.

In the shortest example, all it takes is one line of code.

new MenuFunction(MenuItemDisplayStr(CustTable),MenuItemType::Display).run();

The above code will open the CustTable form. That's all it takes, it's that simple.
Now if you want to supply some arguments to the opening form, this is also possible with the optional args parameter.
Like this for example:

static void OpenFormByCodeA()
{ Args args = new Args();
;
args.record(CustTable::find('ABC'));
new MenuFunction(MenuItemDisplayStr(CustTable),MenuItemType::Display).run(Args);
}

This code will open the CustTable form and filter out the customer with accountnumber ABC.
Use the args methods like parm and parmEnum to provide your target form with more data.

If you want even more control on opening the form from code, this is also possible.
This next example gives the same result as the previous one.

static void OpenFormByCodeB()
{ FormRun formRun;
Args args = new Args();
;
args.name(formstr(CustTable));
args.record(CustTable::find('ABC'));

formRun = ClassFactory.formRunClass(args);
formRun.init();
formRun.run();
formRun.wait();
}


Now if we tweak this a little bit, we can add our code
Like this:

static void OpenFormByCodeB()
{ Object formRun;
Args args = new Args();
;
args.name(formstr(CustTable));
args.record(CustTable::find('ABC'));

formRun = ClassFactory.formRunClass(args);
formRun.init();

formRun.yourmethodgoeshere(); /* !!

formRun.run();
formRun.wait();
}


By changing the type of formRun from class FormRun to class Object, we can implement and execute extra methods on our destination form! This gives us extra possibilities for customizations. You can pass along extra parameters for example.
Only drawback: While programming, your method doesn't show up in the IntelliSense, showing all the available methods. So be carefull of typo's. (They don't give a compile error, but they will give you a run-time error.)

Next up: Run a report by code

Sunday, September 20, 2009

Error: Data source name not found and no default driver specified

When working with ODBC connections in Ax, you may come across this error message:

[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified

Or the complete error message:

ODBC operation failed.

Unable to logon to the database.

[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified

Object 'OdbcConnection' could not be created

Then you check the control panel - Administrative tools - ODBC connections.
And you see your datasource is there. You check the name, and it's spelled ok. So what could be wrong? (Always check the most probable cause of your error message first, a typing error.)

As you might know, the Ax client is a 32 bit application. And nowadays, you might have a 64 bit operating system. Datasources created the default way in a 64 bit operating system cannot be used by Ax. So you need to create a 32 bit datasource.
There is an alternative panel applet to create a 32 bit datasource, fe in Windows Server 2008. You'll find this applet in the C:\Windows\SysWOW64 folder, called odbcad32.exe. (Now watch out, a file by the same name might be available in the C:\Windows\System32 folder. This is NOT the version needed.)

Start the executable C:\Windows\SysWOW64\odbcad32.exe and create your 32 bit datasource. Afterwards, re-execute your code. Did that help make the error go away?
(Some Microsoft info about the issue.)

Create an ODBC connection in Ax

In Ax, you are never on an island. You have lots of options to bring in data from other systems, whether it's through AIF, reading an XML file or.. directly connecting to another database. In this post, I'll show how to create an ODBC connection to a external database.
For this, we'll use the standard available ODBCConnection class, as opposed to CLR Interop and rely on the .NET functions.
Here goes, in 3 simple steps.
  1. Set the login property
  2. Create the connection
  3. Read from a sample table

static void myODBCConnection(Args _args)
{ ODBCConnection myODBC;
Statement myStatement;
LoginProperty myLoginProperty;
Resultset myResultset;

str mySQLStatement;
str myConnectionString;

str myDSN="yourdatasourcenamehere";
str myUserName="yourusername";
str myPassword="yourpassword";
;
myConnectionString=strfmt("DSN=%1;UID=%2;PWD=%3",myDSN,myUserName,myPassword);

myLoginProperty = new LoginProperty();
myLoginProperty.setOther(myConnectionString);

try
{
myODBC = new OdbcConnection(myLoginProperty);
myStatement=myODBC.createStatement();

mySQLStatement="SELECT Field1,Field2 FROM myTable";
myResultSet=myStatement.executeQuery(mySQLStatement);
while (myResultSet.next())
{
info(myResultSet.getString(1));
info(int2str(myResultSet.getInt(2)));
}
} catch
{
error('Unexpected error');
}
}


In earlier versions from Ax, the LoginProperty came with the methods to set the loginname and password. But Microsoft has cut these features (security reasons?). But as you can see in the example, we work around that by using the 'SetOther' method.

There's one thing you need to consider. The Ax client requires a 32 bit datasource. On a 64 bit system like Windows 2008, this may result in an error 'Data source name not found'. That's something for my next post.

Tuesday, September 15, 2009

How to populate a date variable

Some quick tips on how to populate a date variable.

Example 1: The use of function mkdate

TransDate myDate=mkdate(15,9,2009);

This function creates a date based on three integers, which indicate day, month and year. So mkdate(day,month,year).

As an alternative, you can use this:

Example 2: The use of function str2date

TransDate myDate=str2date('15/09/2009',123);

The function str2date converts a text string to a date value. The second argument gives the used formatting of the date string. (Where 1 stands for day, 2 for month and 3 for year.)

There is a similar function to create a DateTime value, namely str2datetime.
Use:

TransDateTime myDateTime=str2datetime( "15/09/2009 20:39:00" ,123 );

So, plenty of choice, whichever suits you.

Thursday, September 10, 2009

Slowly leaving X++, going .NET

Hi,

Everyone knows that, since the acquisition of Navision in 2002, Microsoft has pushed its technology set further and further into the ERP system. They are still working on the 'Project Green' and one single common code base for all the ERP systems.

Now altough Damgaard (the original Axapta founders) has always been very oriented towards Microsoft technology (they did provide an Oracle database option for Axapta, but few have chosen that path), you can do always more. The road ahead is clear, if you see at where Ax is going with the Business Intelligence solutions, all neatly aligned with Microsoft standards.

Talk that the end of X++ (the development language of Ax) is near has been going on for years now. And now the end seems a bit closer again.

Have a look at this video over at Channel 9, talking about X++ and MSIL (MSIL stands for Microsoft Intermediate Language).
So basically, moving closer to .NET again.

Error: "Request for the permission of type 'InteropPermission' failed."

Hi there,

Here's another error message you may come across in Dynamics Ax:

Error: "Request for the permission of type 'InteropPermission' failed."

You have written some code, tested it and everything went fine. Now the project has gone live, and all of a sudden, the code fails with the error message from above.
The code is probably executed in another context as when you tested it. For example by another user, or in batch.
The reason for the failure is the security measures that were implemented in Ax 4.0 and up.
What do you need to do? Assign permissions to execute the code (CLR Interop). Like this:

InteropPermission permission = new InteropPermission(InteropKind::ClrInterop);
;
permission.assert();


If your error message is regarding a COM object that you are referencing, you can use this alternative:

InteropPermission permission = new InteropPermission(InteropKind::ComInterop);
;
permission.assert();

You can read more about CAS or Code Access Security over at MSDN.

xPropertySetData::unpack : Property not found : 1024

We recently came across a very annoying error message:



xPropertySetData::unpack : Property not found : 1024

This happened after an upgrade process. When a user started a specific report and wanted to set a query, the client crashed (Ax32.exe - Fatal Application Exit).
This was only happening to specific users, and with the queries of specific reports. The report itself could be run by the user, as long as the user didn't try to modify the query.
We immediately thought of the last used value, but clearing these for the users didn't help. Clearing the cache: didn't help. Stopping and restarting the AOS (even with deleting the index file): didn't help. Recompile: Nope.
So we investigated this issue a bit further. We found out that the client crashed when executing code in class SysQueryForm, method QueryUnpack.
When trying to create a new query object with old saved data, the client crashed. (new Query(queryPack_V25))
So an easy workaround for us was not to use those old saved queries, just use those of the current version. (replace if (queryPack_V25) with if(false)).

Version: Ax 2009 SP1.
(and as always, use at own risk!)