Wednesday, March 24, 2010

Corrupted Database? Think Again! It Might Simply Be a Corrupted Layer (aod) File.

Recently we had what appeared to be a major issue occur on our Development AX environment. After performing a Synchronize on the Data Directory in the AOT, we received several warning messages, and then when attempting to view various forms we were receiving SQL errors, and what appeared to be missing data.

The initial assumption was that the Database had become corrupted. So we went through the process of doing a backup of the database in our Testing AX environment (which had no issues), and restoring it to the Development AX environment. After the restore however, we still received the same errors as before, which had us puzzled for a moment. Then my colleague, Tim Golisch, remembered something very important.

Not everything in AX is stored in the Database, in fact, everything relating to the layers, are actually stored in AOD files, which reside in the: C:\Program Files\Microsoft Dynamics AX\50\Application\Appl\[DB NAME] folder.

So I stopped the AOS, removed the axuser.aod file, started the AOS, opened the client, and wala! Everything was back to normal.

Monday, March 22, 2010

Binding Table Contents to a Drop Down List in a Grid with MorphX: Displaying Name & Description

In my last post, I demonstrated how to “bind” a Grid field to a table in drop down list. The example demonstrated how to list the Name column, as defined by the Relation in the Extended Data Type; however, in most cases, the list will need to not only display the Name column, but the Description as well. I actually struggled with how this was accomplished, that was, until an expert AX developer pointed me in the right direction. It turns out, given all the places within the AOT where you can see and set various properties to accomplish a desired task (or totally dork something up), the solution is extremely simple. When viewing the Properties on the table being “bound” to, there are two key properties called TitleField1 and TitleField2. Setting TitleField1 = Name and TitleField2 = Description solves this problem, and both the Name and Description data displays in the drop down list.

Thursday, March 18, 2010

Binding Table Contents to a Drop Down List in a Grid with MorphX

Let’s say you have a custom table called SiteList, with two fields: Name and Description, and you want the list of the Name field in this table to appear in a drop down list in a Grid on a Form. Ultimately, achieving this functionality revolves around the use of an Extended Data Type, and setting it up with the proper Relation.

What you need to do is create an Extended Data Type that will be used as the type for your Name field in your SiteList table, and will also be used in the table that will hold the related data (we will build a custom table called SiteVisitors for this example). After you have created your Extended Data Type, and your Table that will be used for the drop down contents, you then need to create a Relation on your Extended Data Type to link it to your Table. After this, things happen on their own, and when this field is used in a Grid (from the SiteVisitors table for example) the Drop Down will automatically appear (there is no need to define the field in the Grid as a ComboBox).

For an example on setting up this scenario do the following:

1. Create an Extended Data Type called: SiteName, extending the String base type.
2. Create a Table called: SiteList
3. Add a String Field to the SiteList (Table) called: Description
4. Drag and Drop the SiteName (Extended Data Type) into the Fields of the SiteList (Table)
5. Go back to the SiteName (Extended Data Type) and expand its nodes.
6. Right-Click the Relations node, and select New > Normal
7. Right-Click the newly created “SiteName == .” node, and select Properties
8. Set the Table property to: SiteList
9. Set the RelatedField property to: SiteName
10. Create a Table called: SiteVisitors
11. Drag and Drop the SiteName (Extended Data Type) into the Fields of the SiteVisitors (Table)
12. Add a new String field to SiteVisitors (Table) called: VisitorName
13. Add a new Date field to SiteVisitors (Table) called: VisitDate
14. Create a new Form called: SiteVisitors
15. Drag and Drop the SiteVisitors (Table) into the Data Sources of the SiteVisitors (Form)
16. Expand the Designs node of the SiteVisitors (Form) and right-click the Design node, and select New Control > Grid
17. Right-Click the Grid, and select Properties
18. Set the DataSource property to SiteVisitors
19. Go back up to the Data Sources node and expand it, then expand the SiteVisitors node, then expand the Fields node.
20. Select the VisitorName, VisitorDate, SiteName fields, and drag and drop them into the Grid.
21. You should now be able to Open the SiteVisitors Form to see things working (of course you might want to put some data into your SiteList Table first).

The image below shows what your project should look like after following the above steps.



Tuesday, March 16, 2010

Using the .NET Business Connector to Export Sales Orders with Multiple Sales Lines into AX from a .NET Application: Part 2

Expanding on the previous blog, let’s say that another requirement has been added, which is, to add the ability to track the date that a record was imported into the system, which will be used to prevent duplicate records for a given customer in a given month from being imported.

The first step would be to add an ImportDate field in the SalesTable via the AOT

The second step would then be to modify our class (ImportToAX) method (ImportSalesOrder) to accept an import date.

The third step would then be to modify our class (ImportToAX) method (ImportSalesOrder) to include logic to determine if a duplicate record (a given customer in a given month) already exists. Programming instinct coupled with my novice skills in AX development tell me that in order to determine if we already have a record for a given customer in a given month, I would add the following lines into our X++ class (ImportToAX) method (ImportSalesOrder):



Select count(RecId) From salesTable
where salesTable.CustAccount == CustAccount && mthofyr(salesTable.ImportDate) == mthofyr(ImportDate) && year(salesTable.ImportDate) == year(ImportDate);
ret = salesTable.RecId;

if (ret == 0)
{
………
}
else
{
throw Global::error("Only one record is allowed per customer in a given month!");
}


This however is invalid code, which throws two errors when compiled. The first being:

Illegal use of WHERE expression

This error is thrown, apparently because methods cannot be used in a where clause, and since I have attempted to use the mthofyr() and year() methods within my where clause, we have an issue. Therefore, one possible way of getting around this, is to take a different approach, such as the following valid code:

found = false;

while select salesTable where salesTable.CustAccount == CustAccount
{
if (mthofyr(salesTable.ImportDate) == mthofyr(ImportDate) && year(salesTable.ImportDate) == year(ImportDate)) found = true;
}

if (!found)
{
………
}
else
{
throw Global::error("Only one record is allowed per customer in a given month!");
}

The second error that we get:

Container and unbounded string (text) fields are not allowed in a WHERE expression

This is inherently related to our method definition, where the CustAccount parameter is defined as an str:

str ImportSalesOrder(str CustAccount, real Total, real Taxes, real Fees, date ImportDate)
{
……
}

The problem is that the salesTable.CustAccount field is actually defined as the extended data type, CustAccount, which extends the base type str. Because AX is a bit finicky, a standard str type cannot be compared to an extended data type (even though the extended data type extends the base type we are comparing to). Therefore, the simplest solution is to make our incoming CustAccount parameter of type CustAccount.

str ImportRoyaltyReport(CustAccount CustAccount, real Total, real Taxes, real Fees, date ImportDate)
{
……
}


This actually has no ill affect the .NET side of the project. In fact, the only parts of our .NET function needing change would be first in the function definition:

static public bool ImportOrder(string CustomerAccount, decimal Total, decimal Tax, decimal Fees, DateTime ImportDate)

And then the code to pass in the additional Import Date parameter to the AX method call:

object[] param = new object[5];
param[0] = CustomerAccount;
param[1] = Total;
param[2] = Tax;
param[3] = Fees;
param[4] = ImportDate;
Object retval = Import.Call("ImportSalesOrder", param);


Finally, to reduce code redundancy in our AX class (ImportToAX) method (ImportSalesOrder), the method could be compacted to avoid having the same block of code repeated 3 times (as in Part 1). Here we can use a couple additional variables, a loop, and a switch statement, which will transform our method as whole to look like the following:

str ImportSalesOrder(CustAccount CustAccount, real Total, real Taxes, real Fees, date ImportDate)
{

SalesTable salesTable;
SalesLine salesLine;
InventDim inventDim;
NumberSeq NumberSeq;

str retval;
int line;
str line_item;
real line_amt;
boolean found;
;

retval = "";

found = false;

while select salesTable where salesTable.CustAccount == CustAccount
{
if (mthofyr(salesTable.ImportDate) == mthofyr(ImportDate) && year(salesTable.ImportDate) == year(ImportDate)) found = true;
}

if (!found)
{

// Generate the next SalesId (which is a Number Sequence)...

NumberSeq = NumberSeq::newGetNumFromCode(SalesParameters::numRefSalesId().numberSequence);
salesTable.SalesId = NumberSeq.num();

// Create the Sales Order...

salesTable.initValue();
salesTable.CustAccount = CustAccount;
salesTable.deliveryDate = today();
salesTable.PurchOrderFormNum = "Imported Data";
salesTable.Tmt_RoyaltyImportDate = ImportDate;

salesTable.initFromCustTable();

salesTable.insert();

// Create the 3 Sales Lines...

for (line = 1; line <= 3; line++) { switch(line) { case 1: line_item = "Total"; line_amt = Total; break; case 2: line_item = "Taxes"; line_amt = Taxes; break; case 3: line_item = "Fees"; line_amt = Fees; } salesLine.clear(); salesLine.SalesId = salesTable.SalesId; inventDim = salesLine.inventDim(); inventDim.InventSiteId = "01"; salesLine.setInventDimIdFromInventDim(inventDim); salesLine.ItemId = line_item; salesLine.SalesQty = 1; salesLine.SalesUnit = 'EA'; salesLine.SalesPrice = line_amt; salesLine.LineAmount = line_amt; salesLine.createLine(NoYes::No, NoYes::Yes, NoYes::No, NoYes::No, NoYes::No, NoYes::No); } retval = salesTable.SalesId; } else { throw Global::error("Only one record is allowed per customer in a given month!"); } return retval; }

Friday, March 12, 2010

Using the .NET Business Connector to Export Sales Orders with Multiple Sales Lines into AX from a .NET Application: Part 1

One requirement that had been given to me was the ability for an existing .NET application to be enhanced to interface with AX. The existing application’s sole purpose was to decode and parse messages being sent to a specified email account, and to export data from these messages into an database. Since the old database and its parent application was going to be slowly phased out with the implementation of AX, the exporting application would need to be enhanced to be able to export key fields of these messages into AX as a Sales Order with multiple Sales Lines. After some thought, I concluded that the solution to this requirement was rather simple in concept, and would need to include an AX Class with one method (used to do the import into the SalesTable and SalesLine tables), as well as an upgrade to the .NET application, utilizing the .NET Business Connector (used to call and execute the AX method).

The basis for my AX method was inspired by a German Dynamics AX Blog, with a title that roughly translates to “Microsoft Dynamics AX API - Part 1: Creating Orders” , by Mathias Füßler. This gave me a good starting point for how to programmatically insert Sales Orders with a related Sales Line. As expected, I needed to make some modifications to the example code for it to work in Dynamics AX 2009, as well as to make it to do exactly what I needed it to do.

The first part of my derived solution for example, was to create a Class in AX, which we will call ImportToAX. This class would contain one key method that we will call ImportSalesOrder. Each Sales Order required a Customer Account number, and three Sales Lines including Total, Taxes, Fees; therefore, the ImportSalesOrder method should accept these required pieces of information as base type parameters.

// This is the only method in the ImportToAX Class
str ImportSalesOrder (str CustAccount, real Total, real Taxes, real Fees)
{

SalesTable salesTable;
SalesLine salesLine;
InventDim inventDim;
NumberSeq NumberSeq;
str retval;
;

retval = "";

try
{

// Obtain the next SalesId (Next Number Sequence)...

NumberSeq = NumberSeq::newGetNumFromCode(SalesParameters::numRefSalesId().numberSequence);
salesTable.SalesId = NumberSeq.num();

// Create the Sales Order...

salesTable.initValue();
salesTable.CustAccount = CustAccount;
salesTable.deliveryDate = today();
salesTable.PurchOrderFormNum = "Imported Data";

salesTable.initFromCustTable();

salesTable.insert();

// Create the first Sales Line...

salesLine.clear();

salesLine.SalesId = salesTable.SalesId;

// The following three lines were added, because AX was requiring a defined site for inventory dimensions…
inventDim = salesLine.inventDim();
inventDim.InventSiteId = "01";
salesLine.setInventDimIdFromInventDim(inventDim);

salesLine.ItemId = ' Total ';
salesLine.SalesQty = 1;
salesLine.SalesUnit = 'EA';
salesLine.SalesPrice = Total;
salesLine.LineAmount = Total;

salesLine.createLine(NoYes::No, NoYes::Yes, NoYes::No, NoYes::No, NoYes::No, NoYes::No);

// Create the second Sales Line...

salesLine.clear();

salesLine.SalesId = salesTable.SalesId;
inventDim = salesLine.inventDim();
inventDim.InventSiteId = "01";
salesLine.setInventDimIdFromInventDim(inventDim);
salesLine.ItemId = 'Taxes';
salesLine.SalesQty = 1;
salesLine.SalesUnit = 'EA';
salesLine.SalesPrice = Taxes;
salesLine.LineAmount = Taxes;

salesLine.createLine(NoYes::No, NoYes::Yes, NoYes::No, NoYes::No, NoYes::No, NoYes::No);

// Create the third Sales Line...

salesLine.clear();

salesLine.SalesId = salesTable.SalesId;
inventDim = salesLine.inventDim();
inventDim.InventSiteId = "01";
salesLine.setInventDimIdFromInventDim(inventDim);
salesLine.ItemId = 'Fees';
salesLine.SalesQty = 1;
salesLine.SalesUnit = 'EA';
salesLine.SalesPrice = Fees;
salesLine.LineAmount = Fees;

salesLine.createLine(NoYes::No, NoYes::Yes, NoYes::No, NoYes::No, NoYes::No, NoYes::No);

retval = salesTable.SalesId;
}
catch
{
retval = "";
}

return retval;
}


The second part of the solution was to create a .NET Class utilizing the .NET Business Connector (which requires a reference to C:\Program Files\Microsoft Dynamics Ax\50\Client\Bin\Microsoft.Dynamics.BusinessConnectorNet.dll). The following .NET Class (C#) contains one core function which is used simply to connect to Dynamics AX, and then to execute the AX method with its required parameters:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Dynamics.BusinessConnectorNet;

namespace ImportToAXExample
{
public class ImportToAXClass
{

static public bool ImportOrder(string CustomerAccount, decimal Total, decimal Tax, decimal Fees)
{
Axapta ax;

bool Success = true;

try
{
ax = new Axapta();
ax.Logon(null, null, null, null);

AxaptaObject Import;
Import = ax.CreateAxaptaObject("ImportToAX");

object[] param = new object[4];
param[0] = CustomerAccount;
param[1] = Total;
param[2] = Tax;
param[3] = Fees;
Object retval = Import.Call("ImportSalesOrder", param);

if (retval.ToString() == "")
{
Success = false;
}

ax.Logoff();
}
catch()
{
Success = false;
}

return Success;
}

}
}

To tie it all together, this .NET Class could then be added to the existing .NET Application, and its only function, ImportOrder, could then be called from the application for each of the records that the application processes. Each time this function is called, required records within the SalesTable and SalesLine tables are created in AX.

Monday, March 8, 2010

Changing the “Original” Server Configuration

After installing the Dynamics AX client on my machine, having set it to connect to a development AOS, I needed to eventually connect the client to the testing AOS. During installation, I was prompted to enter the AOS name, however changing this later was not immediately clear. At first, I assumed this could be done in the AX client, however, after a little bit of digging, I found that changing the AOS connection must be performed via the “Microsoft Dynamics AX Configuration Utility” (Start > Control Panel > Administrative Tools > Microsoft Dynamics AX Configuration).

If you are like me however, you may be curious to know exactly where these settings reside behind the scenes. Using the utility, you cannot change the “Original” configuration, but rather, are expected to create a new configuration which you can then apply and later edit as often as you desire. This serves the purpose well, and I cannot foresee a reason to ever need to modify the original configuration. However, in my industry there are usually exceptions, and obscure needs tend to be required more often than not. So what if for some unknown reason, you need to change the server in the original configuration? This can actually be done quite easily, as the server name is explicitly defined in the following series of registry keys:

HKEY_CURRENT_USER\Software\Microsoft\Dynamics\5.0\Configuration\Original (installed configuration)\

Keys:
- aos2
- internet

HKEY_CURRENT_USER\Software\Microsoft\Dynamics\5.0\Configuration\Original (installed configuration)\SetupProperties

Keys:
- AOS2
- CLIENTAOSSERVER

HKEY_LOCAL_MACHINE\Software\Microsoft\Dynamics\5.0\Configuration\Original (installed configuration)\

Keys:
- aos2
- internet

HKEY_LOCAL_MACHINE\Software\Microsoft\Dynamics\5.0\Configuration\Original (installed configuration)\SetupProperties

Keys:
- AOS2
- CLIENTAOSSERVER


Friday, March 5, 2010

The Basics: Using X++ to throw a user-friendly message on an invalid table insert

Let’s say your customer needs a custom table called VisitorTable, which has an integer field called VisitorCount, and a date field called VisitDate. Duplicate dates are not allowed in the table, and once a record is entered into the system, only the VisitorCount field can be edited. Setting all of this up is rather simple in the AOT strictly using MorphX.

However, in addition, the customer has stated that they need a more user friendly error message displayed when an attempt is made to insert a record with a duplicate date. Performing this task is not quite as straight forward, and requires a little X++. One of any number possible solutions could work, one for example, is as follows:

To accomplish such functionality, you could override the aosValidateInsert () method on the VisitorTable with the following code:

public boolean aosValidateInsert()
{
boolean ret;
VisitorTable vt;
;

select vt
where vt.VisitDate == this.VisitDate;

if (vt.RecId)
{
ret = false;
throw error("The date already exists, you must specify a different date");
}
else
{
ret = super();
}

return ret;
}


This method is executed prior to an insert on the table. “this” is an object reference representing the record that is going to be inserted, thus we use the VisitDate value of “this” as the lookup in our query. If a record is returned matching that date, which is flagged by the existence of a RecId, then we can throw a more user friendly error alerting the user of this issue, otherwise, continue.

CLR Interop in X++ Example 2: Sending Email with an Attachment

Continuing from my first post, “CLR Interop in X++ Example 1: Obtaining a Screen Shot”, the next step in my error reporting routine would be to send the newly created PNG file in an email. Once again, for the .NET Developer, it is easy to first create and test the function in .NET, and use it as a basis for translation into X++. Below is a rather simple C#.NET function which sends an email message with one attachment. The parameters of the function are self-explanatory:

bool SendEmail(string ToAddress, string FromAddress, string SMTPServer, string Username, string Password, string Subject, string Body, string FileToAttach)
{
bool success = true;

try
{
MailMessage mail = new MailMessage(FromAddress, ToAddress);
if (FileToAttach.Length > 0)
{
mail.Attachments.Add(new Attachment(FileToAttach));
}

mail.Subject = Subject;
mail.Body = Body;

SmtpClient smtp = new SmtpClient(SMTPServer);

NetworkCredential networkCred = new NetworkCredential(Username, Password);
smtp.UseDefaultCredentials = false;
smtp.Credentials = networkCred;

smtp.Send(mail);
}
catch
{
success = false;
}

return success;
}


The next snippet is the X++ translation:

boolean SendEmail(str ToAddress, str FromAddress, str SMTPServer, str Username, str Password, str Subject, str Body, str FileToAttach)
{
System.Net.Mail.MailMessage mail;
System.Net.Mail.Attachment attach;
System.Net.Mail.AttachmentCollection ac;
System.Net.Mail.SmtpClient smtp;
System.Net.NetworkCredential networkCred;
boolean success;
;

success = true;

try
{
mail = new System.Net.Mail.MailMessage(FromAddress, ToAddress);
if (strlen(FileToAttach) > 0)
{
attach = new System.Net.Mail.Attachment(FileToAttach);
ac = mail.get_Attachments();
ac.Add(attach);
}

mail.set_Subject(Subject);
mail.set_Body(Body);

smtp = new System.Net.Mail.SmtpClient(SMTPServer);

networkCred = new System.Net.NetworkCredential(Username,Password);
smtp.set_UseDefaultCredentials(false);
smtp.set_Credentials(networkCred);

smtp.Send(mail);
}
catch(Exception::CLRError)
{
success = false;
}

return success;
}

Thursday, March 4, 2010

CLR Interop in X++ Example 1: Obtaining a Screen Shot

As part of a custom error handling routine, I came up with the idea of taking an automatic screen shot of the AX environment at the moment an error occured, which could then be emailed along with other error details to the development team. Whether or not such functionality is actually useful in a production environment is not the point of this entry. This idea rather, was more or less a proof of concept, and my first attempt at utilizing CLR Interop in X++.

The following simple code snippet is a function in C#.NET which simply takes a screen shot of the primary screen, and saves it to a specified location as a PNG file.

bool CaptureScreen(string SaveToFileName)
{
bool success = true;

try
{
Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics graphics = Graphics.FromImage(bitmap as Image);
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
bitmap.Save(SaveToFileName, ImageFormat.Png);
}
catch
{
success = false;
}

return success;

}

The second code snippet is my translation of the code from C# to X++

boolean CaptureScreen(str SaveToFileName)
{
System.Drawing.Bitmap bitmap;
System.Drawing.Graphics graphics;
System.Windows.Forms.Screen primaryScreen;
System.Drawing.Rectangle bounds;
boolean success;
;

success = true;

try
{
primaryScreen = System.Windows.Forms.Screen::get_PrimaryScreen();
bounds = primaryScreen.get_Bounds();

bitmap = new System.Drawing.Bitmap(bounds.get_Width(), bounds.get_Height());
graphics = System.Drawing.Graphics::FromImage(bitmap);

graphics.CopyFromScreen(0,0,0,0, bitmap.get_Size());

bitmap.Save(SaveToFileName, System.Drawing.Imaging.ImageFormat::get_Png());
}
catch
{
success = false;
}

return success;
}



The benefit of first writing a routine in .NET, whether it be in C# or VB.NET, is that you can use the rich GUI of Visual Studio to help you more easily create a CLR based X++ method. Simply by moving the mouse over a given object in your .NET code, you will be able to retrieve the exact namespace of the objects that will need to be explicitly defined in your X++ code. Coming soon, Example 2, which will feature the C# and X++ functions to send the saved PNG in an email...