Thursday, December 30, 2010

How to read records from different company accounts with one select statement

A Dynamics Ax installation can hold information for different company accounts, all in one database. If you wanna share information between different company accounts, you can use Table Collections and Virtual Companies. Maybe you can use the InterCompany module of Ax, in order to automate the information exchange between different company accounts in your setup.

But even if the data is not shared and you don't use InterCompany, you can retrieve data from different company accounts with a simple select statement. In Ax 2009, you can use the crossCompany keyword for this.
All you have to do is add this keyword to your select statement.
Code sample 1: Select records from all company accounts in the database.




static void CrossCompanyTest(Args _args)
{  CustTable CustTable;
;

while select crossCompany CustTable
order by AccountNum asc
{
info(strfmt('%3 - %1 %2',CustTable.AccountNum,CustTable.Name,CustTable.dataAreaId));
}
}
In code sample 2, we limit the company accounts we want to read from.
For this, we can add a container after the crossCompany keyword, that limits the company accounts. The container holds the different wanted company identifiers.

static void CrossCompanyTest(Args _args)
{  CustTable CustTable;
container companyAccountcontainer = ['ca1','ca2'];
;
while select crossCompany : companyAccountcontainer CustTable
order by AccountNum asc
{
info(strfmt('%3 - %1 %2',CustTable.AccountNum,CustTable.Name,CustTable.dataAreaId));
}
}





Note that you cannot use the crossCompany keyword with data modification commands like insert_recordset or delete_from. But with a little workaround and the help of the changeCompany statement, you can still use the crossCompany keyword. Together, they can perform the data updates. More of that later on...

Happy New Year!


Hi there,

A Happy New Year to all my blog readers!
May 2011 bring you, your family and your friends good health and prosperity. I hope all your wishes may come true in this new year.
Let's make it a year to remember!

Lots of new Ax stuff coming up, with the Technical Conference just ahead of us and a new release of Dynamics Ax planned later in 2011. You can expect more blog posts as well over here, so stay tuned!

Sunday, December 26, 2010

How to read group membership information from Active Directory

Last post I talked about reading from Active Directory. This post takes it a little further. We gonna read the user list for a specific group from a given domain.

With this group membership list, you can for example



  • add new users to Dynamics Ax according to an AD group (called 'Ax Users' fe)

  • add users from the Administrators group (AD) to the 'Admin' group in Dynamics Ax

Plenty of applications, for which you can use following job as a base. Again we'll use CLR Interop with the System.DirectoryServices namespace.



static void ReadMoreFromAD(Args _args)

{ System.DirectoryServices.DirectorySearcher DirectorySearcher;

System.DirectoryServices.SearchScope SearchScope;

System.DirectoryServices.DirectoryEntry DirectoryEntry;

System.DirectoryServices.SearchResultCollection SearchResultCollection;

System.DirectoryServices.SearchResult SearchResult;

System.DirectoryServices.PropertyCollection PropertyCollection;

System.DirectoryServices.PropertyValueCollection PropertyValueCollection;



str networkDomain="yourdomainnamehere";

str prefix = 'LDAP://';

int totalCount;

int counter;



str groupName="Administrators";

str groupCrit;



int usercount;

int ucount;

str userinfo;


;

try

{

DirectoryEntry = new System.DirectoryServices.DirectoryEntry(prefix + networkDomain);

SearchScope =CLRInterop::parseClrEnum('System.DirectoryServices.SearchScope', 'Subtree');



DirectorySearcher = new System.DirectoryServices.DirectorySearcher(DirectoryEntry);

DirectorySearcher.set_SearchScope(searchScope);

groupCrit = strfmt('(samaccountname=%1)', groupName) ;

DirectorySearcher.set_Filter(strfmt('(&(objectClass=group)%1)', groupCrit));



SearchResultCollection = DirectorySearcher.FindAll();

totalCount = SearchResultCollection.get_Count();

for (counter=0; counter < totalcount; counter++)
{
SearchResult = SearchResultCollection.get_Item(counter);
DirectoryEntry = SearchResult.GetDirectoryEntry();
if (DirectoryEntry)
{
PropertyCollection = DirectoryEntry.get_Properties();
if (PropertyCollection)
{
PropertyValueCollection = propertyCollection.get_Item('member');
usercount = PropertyValueCollection.get_Count();

for (ucount=0; ucount < usercount; ucount++)
{
userinfo = PropertyValueCollection.get_Item(ucount);
if(userinfo)
info(userinfo);
}
}
}
}

DirectorySearcher.Dispose();
SearchResultCollection.Dispose();

} catch (Exception::CLRError)
{
error("Error reading AD");
return;
}
}

Thursday, December 23, 2010

How to read in Active Directory

Dynamics Ax relies on Active Directory for user authentication. And thanx to CLR Interop, you too can use Active Directory and all it's objects and properties from within Ax. You can use AD for what it is designed for: a central storage location for application data.


But how does one get to read information from the AD? In following code snippet, I'll show you how to collect a list of all users from a specific domain, with some basic information about those users.
For this, we'll use the System.DirectoryServices namespace, an easy way of getting access to Active Directory from managed code.

In order for your code to work, don't forget to edit the networkDomain variable!



static void ReadFromAD(Args _args)
{
System.DirectoryServices.DirectorySearcher DirectorySearcher;
System.DirectoryServices.SearchScope SearchScope;
System.DirectoryServices.DirectoryEntry DirectoryEntry;

System.DirectoryServices.SearchResultCollection SearchResultCollection;
System.DirectoryServices.SearchResult SearchResult;

System.DirectoryServices.PropertyCollection PropertyCollection;
System.DirectoryServices.PropertyValueCollection PropertyValueCollection;

str networkDomain="yourdomainhere.com";
str prefix = 'LDAP://';

int totalCount;
int counter;

str mysamaccountname;
str myusername;
;

try
{
DirectoryEntry = new System.DirectoryServices.DirectoryEntry(prefix + networkDomain);
SearchScope = CLRInterop::parseClrEnum('System.DirectoryServices.SearchScope', 'Subtree');

DirectorySearcher = new System.DirectoryServices.DirectorySearcher(DirectoryEntry);
DirectorySearcher.set_SearchScope(searchScope);
DirectorySearcher.set_Filter(strfmt('(&(objectClass=user))'));

SearchResultCollection = DirectorySearcher.FindAll();

totalCount = SearchResultCollection.get_Count();
for (counter=0; counter < totalcount; counter++)
{
SearchResult = SearchResultCollection.get_Item(counter);
DirectoryEntry = SearchResult.GetDirectoryEntry();

if (DirectoryEntry)
{
PropertyCollection = DirectoryEntry.get_Properties();

if (PropertyCollection)
{
PropertyValueCollection = PropertyCollection.get_Item('samaccountname');
mysamaccountname=PropertyValueCollection.get_Value();

PropertyValueCollection = PropertyCollection.get_Item('name');
myusername=PropertyValueCollection.get_Value();

info(strfmt('%1 - %2',mysamaccountname,myusername));
}
}
}

DirectorySearcher.Dispose();
SearchResultCollection.Dispose();
}
catch (Exception::CLRError)
{
error("Error reading AD");
return;
}

}

Wednesday, November 3, 2010

Ax Trivia - The human factor

Anno 2010, computer code is still written by humans. And though by default you want to take the human factor out of it (think of the sporadic human error), there always stays a bit human-like element in the code, even with all the best practices checking going on.

Thank God those coders have a sense of humor as well, as you can see in this post at GotDAX.

But there are also typos or simple spelling errors in the code. Examples? (From Ax 2009 SP1).

In the Shop Floor module, there exists a class JmgAbcensPreRegistre. It should be absence.
In class LedgerYearAccountDiskBel, there is a variable called transactionsWritenToFile. Written is the correct syntax.
In report SalesPackingSlip, there is a reportcontrol called invoiceLable.

Nitpicking, that's what it is. Some of these errors go years back, which proves renaming isn't always the easiest option. If it ain't broke, don't fix it.

Another trace of humans involved in coding, is the comments you find in the code. They range from type 'obvious' to 'funny'. Again, some examples:

Class LedgerAccrualTrans_Calendar
// so it won't loop endlessly!

Or class LedgerGeneralJournalService\find, which contains some tips from the programmer:

// Performance Gain:
// Another implementation could get all the daily journals that meet the criteria in one statement
// this.findList(_queryCriteria, ledgerGeneralJournal). Then, get another set with all the blocked
// daily journals in one statement. Then filter the result set set in memory. This implementation
// would be faster because we would not make so many database hits.

The same class, method read

// We need to verfiy a few properties before returning the journal.

and

// We are not passing the user group to this message by design because we do not want to
// leak that information. At this point in the development cycle, I can not create a new label.


Class SysExpression\buildXmlDocument

// ax did not like passing the static method call to the clr
// so we load the values into local ax types first


Class SysExpression\buildXPathDocument

// Prime the pump

(had to look that one up: to do something in order to make something succeed, especially to spend money)

Class SysLabelFind\doFormEditControl

tmpTreeNode = tmpTreeNode.AOTparent(); // so walk up the tree


And if you ain't got the time to complete the job, remind yourself (or fellow coder) to finish the job later:
Class SysLabelFind\doFormGroupControl

// TO DO find label of datagroup



I'm pretty sure there are a lot more geeky comments hidden in the code, so if you know any, share them in the comments!

Tuesday, October 19, 2010

How to link an Ax user id to an employee

In different areas in Ax, it’s not so much the user id that is needed, but the employee information. In order not to constantly re-identify yourself in the system, you can link both.

How can a user id be linked to an employee?
Make sure you have created both an employee and a user. Now you are ready to associate them with one another. Go to the Administration - User form and select the desired user. Now press the button User relations. Ax will automatically create a new record, if no link already exists.

Go to the second tab page and under Employee, select the wanted Employee.
You have now successfully linked your Dynamics Ax user to an employee.

Linking a Ax user can either be internal (employee) or external (customer, vendor, business relation).
Same method applies for linking with external contacts.
If desired, you can use a wizard to accomplish the above task as well.

Wednesday, October 13, 2010

How to prevent the error 'Division by zero'

Since the early days of programming, the dreaded 'Division by zero' error exists.

And there is no exception in Dynamics Ax.



So if you're gonna write code like A = B /C, you better make sure that C holds a value.
Of course you can do a check with a simple if-statement (if C then A = B/C), but there is an easier way. You can use minOne, a method that exists in the Global class.


Example:

myvalueA = myvalueB / minOne(myvalueC);


Definition of minOne:

If myvalueC holds a value (is not zero), it's value is returned. If it is zero, the value 1 is returned.
So better be safe then sorry and spare your users the error message (and execution stop).




PS: I know it has been very quite on this blog the last couple of months.
As you can see, I'm not dead. Nor am I planning on ending my blog, I've just been very busy with other stuff. Thanx for your patience.