Monday, April 20, 2015

Error executing code: Insufficient memory to run script. (objects with large buffer ex: xml reponse streams)

​If you see the error "Error executing code: Insufficient memory to run script"
This means that an object/variable within AX is trying to load something into a variable that exceeds the max buffer size for a variable.



When you see this error you have 2 options to fix the issue. 1. reprogram the large object to only load sections at a time. However the bad part about this is you cant just load the large objects in chunks and then combine them into 1  because when you combine them they also can not exceed the max limit. So you would need to grab part 1/4 and display/process. Then dispose of the object and get part 2/4 then dispose and get part 3/4 if you wanted to view 1/4 then you would need to dispose of 3/4 then reload part 1/4.



However you also have option 2 which is you can up the maxbuffersize for variables. This is done via the ax config file (.axc files) that the client loads. This should not be confused with the buffer size set on an AOS server that is normally 48KB. By default AX is set to allow 4mb for this variable buffer size property however in some instances doing the "partial load" doesn't make sense so you need to bump it. For instance when returning large XML data response streams you may need to up this so you can read the response in and then parse it.



If you wish to up the size of the maxbuffersize for variables within X++ the setting that needs to be added to the config file is

maxbuffersize,Text,125



where 125 is the size in MB of the new max buffer size. You can make it 0 which will allow it to be unlimited however that is highly not recommend.

You can also add the change via the registry however in 2012 R2 I did not have much luck getting the client to respect it.
 
Client Registry
Key name: [HKCU\Software\Microsoft\Dynamics\6.0\Configuration\]
Value name: maxbuffersize
Value type: REG_SZ
Value: 125

Monday, April 13, 2015

Cannot create a record in tablename. The record already exists. / Get new recid for a table

I've seen this error a couple times within the past week after we migrated our data from PROD into DEV

"Cannot create a record in <table description> (<table name>). The record already exists."

What I was finding is the index was ok and everything was unique but for some odd reason we kept getting this error, even though I did a search on the data that was being inserted and it did not exist like the system was telling us.

What I figured out is sometimes the recid count gets messed up with transferring data and you need to reset it to a value greater than the max rec id of the current table. Here are the steps to fix it.

1. Create a Job to get the table id of the table having problems inserting data

static void GetTableIdFromNameJob(Args _args)
{
    info(strFmt("%1", tableName2id("<table name>")));
}

2. In sql mgt studio

select max(recid) from <table name from step 1>

3. In sql mgt studio
Edit table SYSTEMSEQUENCES
Select the record where tabid = '<table id from info box in step 1>
Take the max id from step 2 and add 100 to it to be safe and update the field  'nextval' from systemsequences on the tableid you have selected.

4. Restart AOS (the changes will not take affect until you restart the AOS)


Tuesday, April 7, 2015

AX client crashes whenever you check out a label file that is in version control/TFS (Dynamics AX 2012 R2)

I ran across something funky today where whenever we tried to check out or modify a label file it would crash the AX Client.

The following is present within the event viewer logs

 Application: Ax32.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
Stack:
   at Microsoft.Dynamics.Kernel.Client.ActionPaneInterop.ClickEventHelper.Clicked(System.Object, System.EventArgs)
   at Microsoft.Dynamics.Framework.UI.WinForms.Controls.ActionItem.OnButtonClick()
   at Microsoft.Dynamics.Framework.UI.WinForms.Controls.ActionButton.OnMouseUp(System.Windows.Forms.MouseEventArgs)
   at System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
   at System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
   at <Module>._wWinMainCRTStartup()


It appears there is a KB/Hot fix for this issue listed under
KB 2902776, Bug Id 773311: Access violations when synchronizing, checking in or checking out with TFS


It will have you modify the following objects
\Class\SysLabelFile\ & \tables\SysVersionControlSynchronizeLog

Take a look within Lifecycle services for the official code changes on these objects. Its a pretty quick fix though.


Tuesday, March 31, 2015

Duplicate type with name ‘Dynamics.Ax.application.*’ in assembly ‘Dynamics.Ax.application.* (2012 R2)

You may get an error while doing a CIL that says Duplicate type with name ‘Dynamics.Ax.application.*’ in assembly ‘Dynamics.Ax.application.* Which may be a result of you deleting an object and then reimporting it.

A result of this issue is many things within AX may stop working and if you go into

<Company>/System administration/Setup/Services and Application Integration Framework/Inbound Ports you will see many of the services have stopped and can not be restarted thus causing issues within the AX instance.

In order to fix this issue you will need to do the following(steps are listed as if you have a cluster of AOS instances)

  1. Stop all AOS instances
  2. Delete all files on the AOS server under C:\Program Files\Microsoft Dynamics AX\60\Server\<aos instance name>\bin\XppIL
  3. Connect to the model db via SQL Management studio
  4. Truncate the table SYSXPPASSEMBLY which lists all of the files found in step 2
    1. You can do this via the command: TRUNCATE TABLE SYSXPPASSEMBLY
  5. Start 1 AOS instance
  6. Run Full CIL
  7. Start remaining AOS instances


You should now look at the inbound ports to make sure everything now starts correctly. If this does not work you will need to run a full compile of the AOS and repeat the steps listed above.

Wednesday, March 11, 2015

Firming planned order returns error message "Can not edit a record in net requirements(ReqTrans). An Update conflict occurred to another user process deleting or changing one or more fields in the record."

When firming a planned order that has pegged orders you may get the error message

"Can not edit a record in net requirements(ReqTrans). An Update conflict occurred to another user process deleting or changing one or more fields in the record."  which is

 
 
 
 
In order to fix the issue you will need to do the following:
 
Step 1. Update ReqTrans
  • Go to \Tables\ReqTrans
  • Change the property OccEnabled from "yes" to "no"


Check to see if the issue still happens, or if you now get a "forupdate was never selected or incorrect ttsbegin/commit" message go to step 2.


Step 2. Install update KB2910673
  • KB2910673 - When using the "Plan planning formula" window to firm batch orders the system displays the error "Cannot edit a record in Net requirements (ReqTrans)"

Check to see if the issue still happens. If it does go to step 3

Step 3. If you have made it this far then you are experiencing a known issue within AX. Here's what you will need to go in order to fix it.

  • Go to \class\ReqTransPoMarkFirm.updateFirmedReqTrans()
    • At the bottom of the method find the following code
    _reqTrans.InventTransOrigin = _inventTransOriginId;
    _reqTrans.RefType           = _refType;
    _reqTrans.RefId             = _refId;
    _reqTrans.IsForecastPurch   = NoYes::No;
    _reqTrans.update();
 
     
    //delete reqPo
    _reqPO.delete(true);

and replace with the following

variable objects needed:
    ReqTrans            reqTransCopy, updateReqTrans;
    ReqPO               reqPoCopy, removeReqPO;

    //update the transaction record
    ttsBegin;
    select forUpdate * from updateReqTrans where updateReqTrans.RecId == _reqTrans.RecId;
    updateReqTrans.InventTransOrigin = _inventTransOriginId;
    updateReqTrans.RefType           = _refType;
    updateReqTrans.RefId             = _refId;
    updateReqTrans.IsForecastPurch   = NoYes::No;
    updateReqTrans.update();
    ttsCommit;
    //delete the planned order
    ttsBegin;
    delete_from removeReqPO
    where removeReqPO.RecId == _reqPO.RecId;
    ttsCommit;


After completing all 3 steps the issue should be resolved.




Tuesday, February 3, 2015

Get serial reservations from a sales order, sales line or sales parm line

The following below shows you how to get the serial ids or the current qty that is reserved for a sales order, sales line or sales parm line (picking, packing, etc.. form aka SalesEditLines (form))


static void SalesOrderSerialReservations(Args _args)
{
    SalesLine salesLine;
    InventSum inventSum;
    real reserveQty, totalQty;
    InventDim inventDim;
    InventTrans inventTrans;
    InventTransOrigin inventTransOrigin;
    SalesTable salesTable;
    SalesParmLine salesParmLine;
    InventSerial inventSerial;
    container serialIds;

    //get all of the serial reservations for a specific sales order
   while select * from salesTable
        where salesTable.SalesId == '1000970'
   join salesLine
        where salesLine.SalesId == salesTable.SalesId
   join inventTransOrigin
        where inventTransOrigin.InventTransId == salesLine.InventTransId
   && inventTransOrigin.ReferenceCategory == InventTransType::Sales
   join inventTrans
        where inventTrans.InventTransOrigin == inventTransOrigin.RecId
   join inventDim
        where inventDim.inventDimId == inventTrans.inventDimId
   join inventSum
        where inventSum.InventDimId == inventDim.inventDimId
   {
       //reservation qty
       reserveQty =  inventSum.ReservPhysical  - inventSum.ReservOrdered;
      
       info(strFmt("%1    %2",inventDim.inventSerialId,  reserveQty));
   }
  
   
    //get the serials reservations for a specific sales line
    while select * from salesLine
        where salesLine.RecId == 5637223330
    join inventTransOrigin
        where inventTransOrigin.InventTransId == salesLine.InventTransId
    && inventTransOrigin.ReferenceCategory == InventTransType::Sales
    join inventTrans
        where inventTrans.InventTransOrigin == inventTransOrigin.RecId
    join inventDim
        where inventDim.inventDimId == inventTrans.inventDimId
    join inventSum
        where inventSum.InventDimId == inventDim.inventDimId
    {
         reserveQty =  inventSum.ReservPhysical  - inventSum.ReservOrdered;
         info(strFmt("%1    %2",inventDim.inventSerialId,  reserveQty));
    }

    //get the serial reservations for a specific sales line from the salesparmline
    while select RecId,SalesLineRecId  from salesParmLine
        where salesParmLine.RecId == 5637223330
    join RecId,InventTransId from salesLine
        where salesLine.RecId == salesParmLine.SalesLineRecId
    join RecId, InventTransId, ReferenceCategory from inventTransOrigin
        where inventTransOrigin.InventTransId == salesLine.InventTransId
            && inventTransOrigin.ReferenceCategory == InventTransType::Sales
    join InventTransOrigin, inventDimId from inventTrans
        where inventTrans.InventTransOrigin == inventTransOrigin.RecId
    join inventDimId, inventSerialId from  inventDim
        where inventDim.inventDimId == inventTrans.inventDimId
    join InventDimId, ReservPhysical, ReservOrdered from inventSum
        where inventSum.InventDimId == inventDim.inventDimId
    join inventSerial
        where inventSerial.InventSerialId == InventDim.inventSerialId
    {
        //reservation qty
        reserveQty =  inventSum.ReservPhysical  - inventSum.ReservOrdered;
        //total reservation qty
        totalQty += reserveQty;
        //serial ids that are reserved
        serialIds += inventDim.inventSerialId;
    }
   
    info(strFmt("%1    %2",con2Str(serialIds), totalQty ));
}

Friday, January 23, 2015

Add trailing 0's to a number to display to a user. Ex: 12.78 VS 12.7800

So if you have dealt with displaying and transferring numbers that have trailing 0's within AX you will notice that all trailing 0's get dropped when you display the value to the user via code or try to use the variable in any way except tying the value/field directly to a control even though they get stored within the DB with trailing zeros.

For example if you display the following

real test1;

test1 = 2.3450000;

info(strFmt("%1", test1));




The user will be given/displayed a value of 2.345 instead if 2.3450000  most people wouldn't mind this but if you work somewhere that has to display the full length of a value tested then this no longer works.

The following method will take in any real value and apply either a default of 3 decimals or allow you to override that value with anything you specify



 /// <summary>
/// Add trailing zeros to a number if needed and return as string so we can display and pass it to other functions without 0's being droped
/// </summary>
/// <param name="numberToModify">
/// Number to add trailing 0's to
/// </param>
/// <param name="overrideDefaultDecimal">
/// Should we override the default decimal count of 3 and provide our own
/// </param>
/// <param name="decimalOveride">
/// Number of decimals to apply to number (override)
/// </param>
/// <returns>
/// String value with trailing 0's
/// </returns>
static str AddTrailingZeros(FWMDecimalToConvert numberToModify, boolean overrideDefaultDecimal = false,  int decimalOveride = 0)
{
    str formatType = "N";
    System.Double resultTemp;
    str formattedResult;
    int numberOfDecimals = 3;
   
    //check to see if we should override the default decimal precision
    if(overrideDefaultDecimal)
    {
        numberOfDecimals = decimalOveride;
    }
    //convert the rounded value to a double type so we can use outside classes to handle adding trailing zeros
    resultTemp = System.Convert::ToDouble(numberToModify);

    //convert the converted value back to string with trailing zero's
    formattedResult = resultTemp.ToString(formatType + int2str(numberofDecimals));
    return formattedResult;
}



So now if you call the following

real test1, test2;

test1 = 2.3450000;

test2 = 2.1;

info(strFmt("%1 vs %2", test1, ClassName::AddTrailingZeros(test2)));



You will get 2.35 vs 2.100

or you could call info(strFmt("%1 vs %2", test1, ClassName::AddTrailingZeros(test2, true, 7)));

You will get 2.35 vs 2.1000000

and you can then transfer that value anywhere without anything ever being dropped. The downside to this method is your variable(number) is now stored as a string instead of a real. I guess you cant have your cake and eat it to.

This method uses .net formatting functions to accomplish this so you could also change this function to format as percentages, dollars or whatever you like. This is defined via 'formatType = "N"'  You can check out the available format types at https://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx



Friday, December 19, 2014

How to list all of the files in a folder and sub folder via AX

The following examples will show you how to list all of the files in a folder and sub folder.

 static void GetFilesInFoldersAndSubFolders(Args _args)
{
    System.String[] filePaths = System.IO.Directory::GetFiles(@"folder location", "*.*", System.IO.SearchOption::AllDirectories); //get listing of all files within the folder
    int fileCount = filepaths.get_Length(); //get how many files were found
    int currentFileCount;
   
    //go throw each one of the files that were found
    for(currentFileCount = 0; currentFileCount < fileCount ; ++currentFileCount)
    {
        info(filepaths.GetValue(currentFileCount));
    }
}

Send xml to BarTender via TCP sockets and get response back using UTF-8 encoding

This is an example that can be used to send print jobs/print previews (return jpgs/images via raw text) to the BarTender labeling system or using this example as a foundation to talk to your own servers via tcp sockets

 /// <summary>
/// Send print job btxml string to bartender and get a response back
/// </summary>
/// <param name="defaultXML">
/// the BTXML to send to bartender
/// </param>
/// <returns>
///BarTenderStaus if it was successfull or not
/// </returns>
public BarTenderStaus sendPrintJobTCP(str defaultXML)
{
    str                 response;
    XmlTextReader       xmlTextReader;
    boolean             msgFound;
    System.Text.Encoding encoding;
    str                 errorId;
    boolean             errorsFound;
    str                 message;
    SysOperationProgress previewProgress;
    System.Net.Sockets.TcpClient barTenderClient;
    System.Net.Sockets.NetworkStream stream;
    System.IO.StreamReader reader;
    System.Byte[] send_bytes;
    #avifiles
    try
    {
        //setup progress bar so we can let the user know what we are doing
        previewProgress = SysOperationProgress::newGeneral(#AviPublish2Web, 'Sending Print Job To BarTender', 7);

        if(serverSettings.BarTenderServerName == "" || serverSettings.BarTenderServerPortNumber == 0)
        {
            throw error("No valid BarTender server has been defined. Please contact IT.");
        }
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Reading XML...");

        message = defaultXML;
        //check to see if we have something available to send to the server
        if(message == "")
        {
            throw error("No items are available to print");
        }

        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Connecting to BarTender Server...");
        //make connection to bartender and open stream with UTF8 encoding
        barTenderClient = new System.Net.Sockets.TcpClient(serverSettings.BarTenderServerName, serverSettings.BarTenderServerPortNumber);
        stream = barTenderClient.GetStream();
        reader = new System.IO.StreamReader(stream, System.Text.Encoding::get_UTF8());

        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Sending Label to BarTender...");
        //define the stream encoding as UTF8
        encoding = System.Text.Encoding::get_UTF8();
        //convert the xml to bytes to send to bartender
        send_bytes = encoding.GetBytes(message);
        //send the xml bytes to bartender
        stream.Write(send_bytes, 0, send_bytes.get_Length());
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Reading Response From BarTender...");
        //get the response bartender sends back
        response = reader.ReadToEnd();
        //close the bartender stream
        stream.Close();
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Analyzing XML Response from BarTender...");
        //we need to parse the xml response string into xml nodes
        xmlTextReader = XmlTextReader::newXml(response, true);
        //loop through all of the xml nodes
        while(xmlTextReader.read())
        {
            //check to see what type of xml node we are currently on
            switch (xmlTextReader.NodeType())
                {
                    case XmlNodeType::Element:
                        //check to see if we are on the message node
                        if(xmlTextReader.Name() == "Message")
                        {
                            //read the message attributes
                            while (xmlTextReader.MoveToNextAttribute())
                            {
                                //check to see if the attributes node is severity
                                if(xmlTextReader.Name() == "Severity")
                                {
                                    //check to see if we are looking at an error
                                    if(xmlTextReader.Value() == "Error")
                                    {
                                        //error has been found
                                        msgFound = true;
                                        errorsFound = true;
                                    }
                                }
                                else if(xmlTextReader.Name() == "Id")
                                {
                                    //define what the id attribute is incase it is an error
                                    errorId = xmlTextReader.Value();
                                }
                            }
                        }
                        break;
                    case XmlNodeType::Text:
                        //check to see if the last node we were on was a message and if it was an error
                        if(msgFound)
                        {
                             //display error message to user in a friendly manner
                             setPrefix("BarTender Error " + errorId);
                             error(xmlTextReader.Value());
                             //reset message found flag
                             msgFound = false;
                        }
                        break;
                    case XmlNodeType::EndElement: //Display the end of the element.
                        break;
                    default:
                        break;
                }
        }

        //update progress bar
        previewProgress.setText("Task: Finished");
        previewProgress.setTotal(7);
        previewProgress.hide();
        //check to see if any errors occured
        if(!errorsFound)
        {
            return BarTenderStaus::Success;
        }
        else
        {
            return BarTenderStaus::Failure;
        }
    }
    catch
    {
        error("There was an unknown error communicating with the BarTender server. Please restart your computer and try again. If the issue continues please alert IT of this issue.");
        //update progress bar
        previewProgress.setText("Task: Finished");
        previewProgress.setTotal(7);
        previewProgress.hide();
        return BarTenderStaus::Failure;
    }
}

Thursday, October 23, 2014

C# Accessing AX Session Information & Defining which AOS to connect to via code

So while working on the last post I ran into an issue where I couldn't change which aos the data was being pulled in from so I wrote the following function that gets server information so you can see basic information on what connection is being used. Along with defining which AOS the code pulls from by defining a server within code so I can switch back and forth between DEV & TEST

You can download the C# visual studio project here: download now!


Its good to note that around the web the following "logon" statement is posted

// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, null, null);

However in order to define which AOS server to connect to via code we need to do the following

// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, "AOSServerName", null);




The following code are just some ways to pull in basic server information


private void GetAOSInfo_Click(object sender, EventArgs e)
{
 
try
{
 
// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, axServerName, null);


 
//get server information

string axInfoAOSInstance = axSession.CreateObject("XSession").Call("AOSName").ToString();
string axInfoClientName = axSession.CreateObject("XSession").Call("clientComputerName").ToString();
string axInfoLoginDateTime = axSession.CreateObject("XSession").Call("loginDateTime").ToString();
string axInfoMasterSessionId = axSession.CreateObject("XSession").Call("sessionId").ToString();
string axInfoUserId = axSession.CreateObject("XSession").Call("userId").ToString();



CustomerOutput.Clear();
 
CustomerOutput.AppendText(("AOS Server: " + axInfoAOSInstance));
CustomerOutput.AppendText((Environment.NewLine + "Client Computer Name: " + axInfoClientName));
CustomerOutput.AppendText((Environment.NewLine + "Session Id: " + axInfoMasterSessionId));
CustomerOutput.AppendText((Environment.NewLine + "Login DateTime: " + axInfoLoginDateTime));
CustomerOutput.AppendText((Environment.NewLine + "User Id: " + axInfoUserId));



 
 
//log off ax
axSession.Logoff();

}
 
catch (Exception ex)
{

CustomerOutput.Clear();

CustomerOutput.AppendText(ex.Message.ToString());

}

}