Wednesday, April 29, 2009

Getting the stacktrace

Hi,


X++ has this little function built in the class xSession, which gives you the stacktrace. This may come in handy for you in special circumstances (fe together with the infolog of an error).

Syntax:
xSession::xppCallStack()

The function returns a container, so handle accordingly.
If you want to show the current stacktrace:
info(con2str(xSession::xppCallStack()));

Best of luck,


Willy.

Monday, April 27, 2009

Application Benchmarking Toolkit for Microsoft Dynamics AX

Hey all,

Microsoft has released a seperate, freely available solution to test your Ax 2009 system.

Check it out at

http://benchmarktoolkit.codeplex.com/


Released under open source license, approx. 11 Mb.

Happy stress and performance testing!


Willy

Sunday, April 26, 2009

Number of records in a form

Have you ever needed the number of rows that are returned by the executeQuery method on the datasource of a form? For example to show some progress display while you evaluate each record individually?



Not available in earlier versions of Dynamics Ax aka Axapta, but now it is:



yourdatasource_ds.numberOfRowsLoaded();



Easier then doing a select count statement, not?

(The MSDN help information (Ax 2009 SDK) is rather limited for this function.)


Greetings,



Willy.

Friday, April 24, 2009

List of modified AOT objects

You are in an upgrade process. You browse the AOT to check for modified elements. Sometimes it's easy to spot them (bold typeface, marked USR), but sometimes it's not (they seem to belong to the SYS-layer, but when expanded they reveal themselves as belonging to another layer).

This short script will give you a list of all "touched" objects.

static void CheckLayer(Args _args)
{ TreeNode TreeNode;
TreeNode BaseTreeNode;
UtilEntryLevel UtilEntryLevel;
#AOT
;
BaseTreeNode = TreeNode::findNode(#TablesPath);

TreeNode = BaseTreeNode.AOTfirstChild();

while(TreeNode)
{
UtilEntryLevel=treeNode.applObjectLayer();
if((UtilEntryLevel==UtilEntryLevel::usr) (UtilEntryLevel==UtilEntryLevel::var))
{
info(strfmt("Object %1 - layer %2",TreeNode.AOTname(),enum2str(UtilEntryLevel)));
}
TreeNode=TreeNode.AOTnextSibling();
}
}

This script gives you all the tables modified in either the VAR or USR layer.
Replace the #TablesPath macro name as needed, to get EDT, classes, forms, ...

Dynamics Ax provides some nice upgrade tools, but sometimes the "manual" approach is needed.

Kind regards,

Willy

Thursday, April 23, 2009

Missing stored procedures

When you upgrade Ax from V3 to V4 or from V3 to V2009, you might not be able to start the new AOS instance. The application event log may show an event similar like this:

Object Server 02: Internal Error occurred executing stored procedure when creating session for the AOS.

Cause:
The SQL remote procedures CREATESERVERSESSIONS and CREATEUSERSESSIONS are not created automatically in your upgrade database.

Solution:
1) Copy the stored procedures from a working version (database V4 or V2009, by using fe SQL Server Management Studio)
or
2) Use following scripts.

You can use the following scripts to create the necessary procedures:


USE [YourDatabaseNameGoesHere]
GO
/****** Object: StoredProcedure [dbo].[CREATESERVERSESSIONS] Script Date: 10/05/2008 15:15:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[CREATESERVERSESSIONS] @aosId varchar(50), @version int, @instanceName nvarchar(50), @recid bigint, @maxservers int, @status int, @loadbalance int, @workload int, @serverid int OUTPUT as declare @first as varchar(50) declare @max_val as int begin select top 1 @first = SERVERID from SYSSERVERSESSIONS WITH (UPDLOCK, READPAST) where STATUS = 0 and AOSID = @aosId and INSTANCE_NAME = @instanceName if (select count(SERVERID) from SYSSERVERSESSIONS where SERVERID IN (@first)) > 0 begin update SYSSERVERSESSIONS set AOSID = @aosId, VERSION = @version, INSTANCE_NAME = @instanceName, LOGINDATETIME = dateadd(ms, -datepart(ms,getutcdate()), getutcdate()), LASTUPDATEDATETIME = dateadd(ms, -datepart(ms,getutcdate()), getutcdate()), STATUS = @status, WORKLOAD = @workload where SERVERID IN (@first) and ((select count(SERVERID) from SYSSERVERSESSIONS where STATUS = 1 and LOADBALANCE = 0) < @maxservers) if @@ROWCOUNT = 0 select @serverid = 0 else select @serverid = @first end else begin if (select count(SERVERID) from SYSSERVERSESSIONS WITH (UPDLOCK) where STATUS = 1 and LOADBALANCE = 0) >= @maxservers select @serverid = 0 else begin if (select count(SERVERID) from SYSSERVERSESSIONS) = 0 select @max_val = 1 else select @max_val = max(SERVERID)+1 from SYSSERVERSESSIONS insert into SYSSERVERSESSIONS(SERVERID, AOSID, INSTANCE_NAME, VERSION, LOGINDATETIME, LASTUPDATEDATETIME, STATUS, RECID, LOADBALANCE, WORKLOAD) values(@max_val, @aosId, @instanceName, @version, dateadd(ms, -datepart(ms,getutcdate()), getutcdate()), dateadd(ms, -datepart(ms,getutcdate()), getutcdate()), @status, @recid, @loadbalance, @workload) select @serverid = @max_val end end end


And


USE [YourDatabaseNameGoesHere]
GO
/****** Object: StoredProcedure [dbo].[CREATEUSERSESSIONS] Script Date: 10/05/2008 15:17:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE procedure [dbo].[CREATEUSERSESSIONS] @clientType int, @sessionType int, @serverid int, @versionid int, @userid varchar(5), @lanExt varchar(10), @manExt varchar(10), @sid varchar(124), @recid bigint, @startId int, @maxusers int, @licenseType int, @masterId int, @maxClientId int, @sessionid int OUTPUT, @loginDateTime datetime OUTPUT as declare @return_val as int declare @first as int declare @max_val as int begin select @sessionid = -1 select @max_val = -1 select @loginDateTime = dateadd(ms, -datepart(ms,getutcdate()), getutcdate()) if(not exists(select * from SYSSERVERSESSIONS WITH (NOLOCK) where SERVERID = @serverid AND Status = 1)) begin select @sessionid = -2 return end select @first = min(SESSIONID) from SYSCLIENTSESSIONS WITH (UPDLOCK,READPAST) where STATUS = 0 and SESSIONID > @maxClientId and SESSIONID <> @masterId if (select count(*) from SYSCLIENTSESSIONS where SESSIONID IN (@first)) > 0 begin if (@licenseType = 0) begin update SYSCLIENTSESSIONS set STATUS = 1, VERSION = @versionid, SERVERID = @serverid, USERID = @userid, LOGINDATETIME = @loginDateTime, SID = @sid, USERLANGUAGE = @lanExt, HELPLANGUAGE = @manExt, CLIENTTYPE = @clientType, SESSIONTYPE = @sessionType where SESSIONID IN (@first) end else if (@licenseType = 1) begin update SYSCLIENTSESSIONS set STATUS = 1, VERSION = @versionid, SERVERID = @serverid, USERID = @userid, LOGINDATETIME = @loginDateTime, SID = @sid, USERLANGUAGE = @lanExt, HELPLANGUAGE = @manExt, CLIENTTYPE = @clientType, SESSIONTYPE = @sessionType where SESSIONID IN (@first) and ((select count(SESSIONID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and ((STATUS = 1) or (STATUS = 2))) < @maxusers) end else if (@licenseType = 2) begin update SYSCLIENTSESSIONS set STATUS = 1, VERSION = @versionid, SERVERID = @serverid, USERID = @userid, LOGINDATETIME = @loginDateTime, SID = @sid, USERLANGUAGE = @lanExt, HELPLANGUAGE = @manExt, CLIENTTYPE = @clientType, SESSIONTYPE = @sessionType where SESSIONID IN (@first) and ( (select count(SESSIONID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and (USERID = @userid) and ((STATUS = 1) or (STATUS = 2))) > 0 or (select count(distinct USERID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and ((STATUS = 1) or (STATUS = 2))) < @maxusers ) end if @@ROWCOUNT = 0 select @sessionid = 0 else select @sessionid = @first end else begin if (@licenseType = 1) begin if (select count(SESSIONID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and ((STATUS = 1) or (STATUS = 2))) >= @maxusers select @sessionid = 0 end else if (@licenseType = 2) begin if ( ((select count(distinct USERID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and ((STATUS = 1) or (STATUS = 2))) >= @maxusers) and ((select count(SESSIONID) from SYSCLIENTSESSIONS where CLIENTTYPE = @clientType and (USERID = @userid) and ((STATUS = 1) or (STATUS = 2))) = 0) ) select @sessionid = 0 end if (@sessionid = -1) or (@licenseType = 0) begin if (select count(SESSIONID) from SYSCLIENTSESSIONS WITH (UPDLOCK) where STATUS = 0 or STATUS = 1 or STATUS = 2 or STATUS = 3) = 0 select @max_val = @startId else select @max_val = max(SESSIONID)+1 from SYSCLIENTSESSIONS WITH (UPDLOCK) if (@max_val > 65535) select @sessionid = -3 else begin insert into SYSCLIENTSESSIONS(SESSIONID, SERVERID, VERSION, LOGINDATETIME, USERID, SID, USERLANGUAGE, HELPLANGUAGE, CLIENTTYPE, SESSIONTYPE, RECID, CLIENTCOMPUTER, STATUS) values(@max_val, @serverid, @versionid, @loginDateTime, @userid, @sid, @lanExt, @manExt, @clientType, @sessionType, @recid, '', 1) if @@ROWCOUNT = 0 select @sessionid = -1 else select @sessionid = @max_val end end end end


Don't forget to replace the dummy databasename at the start of the procedure (YourDatabaseNameGoesHere).

Good luck,

Willy.

Wednesday, April 22, 2009

The Main Menu is dead, long live the Main Menu

In Ax 2009, the user interface underwent some drastic changes. The victim seems to be the MainMenu.
Do you still like the old style Ax (Axapta)? Fond memories of the MainMenu?
Here is a simple job, that shows the classic MainMenu.

static void ShowMainMenu(Args _args)
{ Menu Menu;
#admin
;
Menu = new Menu(#MainMenu);
Menu.AOTrun();
}

This should get you going to show the main menu at startup.

Greetings,
Willy

Tuesday, April 21, 2009

Fellow bloggers

There are some interesting blogs about Dynamics Ax out there.
Some updated frequently, some less frequently, but none the less very interesting.

Just to get you started:

http://palleagermark.blogspot.com/
http://gatesasbait.spaces.live.com/
http://dynamicsax-dev.blogspot.com/
http://dynamics-ax.blogspot.com/
http://www.crankturner.com/
http://daxguy.blogspot.com/

Happy reading!


Willy

Saturday, April 18, 2009

AOS crash when synchronizing

When you perform a synchronize of all the tables in the AOT during an upgrade process, the AOS may crash, leaving following message in the event log:

Binding operation failed to allocate buffer space

Workaround:
In the Server Configuration Utility, tabpage Database tuning edit the field Maximum buffer size. A value of 5120 may do the trick for you.
Restart the AOS service and try synchronizing again.

Good luck,


Willy.

Thursday, April 16, 2009

Client crashes with import

After an upgrade to Ax 2009 (no SP or SP1), when you try to import something in the AOT, the client crashes. The crash happens before you get the dialog screen.
The event log shows a general message 'Faulting application Ax32.exe'.

Problem may be with a custom label file.

Workaround: Edit AOT, Class SysLabel, method findModules.
Set following line in comment:

label = new Label();
// label.searchFirst('');
labelModuleId = label.getFirstLabelFile();

Real solution: Let Axapta recreate the index files for the labels. For this, stop the AOS, delete the *.ALC and *.ALI label files and restart the AOS. (The *.ALD files hold the actual label information.)


Good luck,


Willy.

Wednesday, April 15, 2009

Yet another new Ax blog

Hi,

Welcome to yet another new blog about Microsoft Dynamics Ax.
This blog will combine technical and functional topics about this great ERP system that Ax really is.

My intention is to post tips, tricks, upgrade experiences, real life events, and so on.

I work for an end-user, so I am not on the Microsoft staff. I live and work in Europe.


Comments and questions are welcomed.

Willy