Search This Blog

Wednesday, December 21, 2011

Search un-used EDT'S using X++


To find unused EDT’s in your Dynamics Ax application, the following code will sscan through the crossreference and display the EDT’s on screen.
Note, you will have to compile your application with crossreference turned on, otherwise you may get all your EDT’s as not used
//True if used
private boolean FindEDTUsed(extendedTypeId _id, str 40 name)
{
   XRefNames   xRefName;
   boolean ret = true;
   ;


   select firstonly xRefName
           where xRefName.kind == xRefkind::ExtendedType
            && (xRefName.name == name);

  if(xRefName.RecId)
  {
    ret = true;
  }
  else
  {
   ret = false;
  }
  return ret;
}

public void run()
{
  Dictionary dict = new Dictionary();
  int types;
  int i;
  int first = true;
  Set         unusedEDT;
;
 unusedEDT = new Set(Types::Integer);
 //Find list of EDT
 types = dict.typeCnt();
 info(dict.typeName(dict.typeCnt2Id(1)));

  for(i = 1; i <= types; i++)
  {
  //process each type
    if(!this.FindEDTUsed(dict.typeCnt2Id(i), dict.typeName(dict.typeCnt2Id(i))))
    {
       if(first == true)
       {
         info('Unused EDT List');
         first = false;
       }
       //unusedEDT.add(dict.typeCnt2Id(i)); 
       //store it in a set for later use
       info(dict.typeName(dict.typeCnt2Id(i)));
    }
  }
}

Validate Table Fields using X++



The following job is used to validate the fields same as like validatefield method in the form for the table.

You can create this method as the static method in any of your class and call that method and it will returns the table got valid data or not.

static server Boolean krishh_validateTable(common _common)
{
   boolean ret = true;
   SysDictTable    dictTable;
   int fieldCnt, i;
   fieldId fieldid;
   ;

    dictTable = new SysDictTable(_common.TableId);
    fieldCnt= dictTable.fieldCnt();
    for(i = 1; i <= fieldCnt; i++)
    {
       fieldid= dictTable.fieldCnt2Id(i);
       ret = ret &&;
       _common.validateField(fieldid);
    }
    return ret;
}

Friday, December 16, 2011

Sales Invoice using X++

You can use this job to generate the sales Invoice Report.
SalesConfirm report can be done by replacing the reportname,salesformletter_confirm,and object shouldbe custconfirmJournal.

 static krishh_TestSalesInvoiceReport(Args  _args)
   {
    Args                args;
    ReportRun           reportRun;
    SalesFormLetter     salesFormLetter;
    PrintJobSettings    printJobSettings;
    CustInvoiceJour InvJTbl;
    RecordSortedList    list                = new RecordSortedList(62);
    SalesId   Id ;
    ;

     Id = "SO-0000123";
    Select InvJTbl Where InvJTbl.SalesId == Id;
    List.ins(InvJTbl);
    args = new Args(ReportStr(SalesInvoice));


    printJobSettings = new PrintJobSettings();
    printJobSettings.SetTarget(PrintMedium::Printer);
    printJobSettings.suppressScalingMessage(true);

    salesFormLetter  = new SalesFormLetter_Invoice(false);
    salesFormLetter.updatePrinterSettingsFormLetter(printJobSettings.packPrintJobSettings());

    args.designName("Standard");
    args.caller(salesFormletter);
    args.parmEnum(PrintCopyOriginal::Original);
    args.parmEnumType(enumnum(PrintCopyOriginal));
    args.object(list);

    reportRun = new ReportRun(args);
    reportRun.setTarget(PrintMedium::Printer);
    reportRun.init();
    reportRun.run();
}|

Thursday, December 1, 2011

AX2012 Reporting with MVC Architecture

New AX 2012 Reporting Development model based on MVC Design Pattern?"
The short answer to this question, is Yes!.

For those that are familiar with the concepts behind MVC, or Model-View-Controller, then this is a great move in reporting. If your not that familiar with the MVC concepts, wikipedia.org has a great little stub home for this topic- http://en.wikipedia.org/wiki/Model-view-controller

"Model–view–controller (MVC) is a software architecture,[1] currently considered an architectural pattern used in software engineering. The pattern isolates "domain logic" (the application logic for the user) from the user interface (input and presentation), permitting independent development, testing and maintenance of each (separation of concerns).

Model View Controller (MVC) pattern creates applications that separate the different aspects of the application (input logic, business logic, and UI logic), while providing a loose coupling between these elements."
Now, lets get to the specifics for Dynamics AX 2012, around this concept and how this design pattern is applied for reporting. First off, the following will give you a high level understanding of how the MVC pattern maps to real concepts, and class frameworks in AX 2012, as well as a link to the recently released programming guide for AX 2012.: MSDN: Reporting Programming Guide [AX 2012].

Model: SrsReportDataContractUIBuilder
View: SrsReportDataContract
Controller: SrsReportRunController

Finally, you have the following which is a direct link to download the Report Programming Guide for AX 2012: [AX 2012] Report Programming Guide

For now, this is a good start, and I thought a good way to start the week. Focused on the answer to a question, with some great resource information from Microsoft. Yes, the reporting programming modeling in AX 2012 is based around MVC.

Ax2012 CU2 Released.

 Wanted to update everyone that Microsoft Dynamics AX 2012 - CU2 has been released. You can get to the download from the following location: Cumulative Update 2 for Microsoft Dynamics AX 2012

In this update several fixes are included, and the release schedule for AX 2012 is very impressive from Microsoft for updating issues. The new build number, after Cu2 is applied should be: 6.0.947.280.

There are a lot of fixes around workflow, financial dimensions, EP Time sheets, etc. etc. It's important that everyone update to this latest CU2, to help address any issues, and before contacting MS Support for new issues.

Keep in mind, that these Cumulative Updates, can be SlipStreamed for fast deployment. To find out exactly how to do this action, you can use the following post: AX 2012 - Slipstreaming a Cumulative update

That's all for now, check back soon as more to come! Till next time!

Update: Interesting fact here, if you have for example an AOS server that does not have the 32-bit client installed on it, you can not proceed with the CU2 update. So you must have the AX 2012 client installed before the axupdate.exe from CU2 will progress. Interesting point I thought everyone would like to know.

Upgrading Reports [AX 2012]

MorphX reports as well as ReportLibraries can still live within AX 2012. There is actually a process in which you have to execute that will take the metadata for the AX 2009 ReportLibraries and update them, so they can live and execute within an AX 2012 instance. That resource can be found here.: Report Project Upgrade [AX 2012]

Guidance when upgrading the report
http://technet.microsoft.com/en-us/library/gg724106.aspx

Wednesday, November 30, 2011

how to use Set class in AX

The Set class is used for the storage and retrieval of data from a collection in which the values of the elements contained are unique and serve as the key values according to which the data is automatically ordered.
Scenario:
I have this requirement to track the records that are modified by user and  i  need to take action on mofdied records
Solution with SetsDeclare set in Form\class declaration
Set                         setChangedRecord;
Initialize set on Form\DataSource\init as follows:
setChangedRecord     = new Set(Types::Record);
Store records in set on Write method of the data source as follows
 if (!setChangedRecord.in(purchReqLine))
    {
        setChangedRecord.add(purchReqLine);
    }
Traversing of records in Set:
Following is the way of traversing records from Set
SetEnumerator   setEnumerator    = setChangedRecord.getEnumerator();
 while (setEnumerator.moveNext())
            {
                reqLine = setEnumerator.current();
                if (!reqLine.IsSaved)
                {
                    purchReqLineLocal = PurchReqLine::findRecId(reqLine.RecId,true);
                    purchReqLineLocal.IsSaved = true;
                    purchReqLineLocal.update();
                }

            }

Difference between Container, List, Map and set

Containers:Containers are dynamic and have no limits. They can contain elements of
almost all data types: boolean, integer, real, date, string, container,
arrays, tables, and extended data types. However, objects may not be stored
in containers.
Containers in AX are used very often. It’s easy to work with them. But…
data in containers are stored sequentially, and thus retrieved sequentially.
This means that containers provide slower data access if you are working with
_large numbers_ of records. In case of large numbers of records use temporary
tables.
List:
Lists are structures that may contain any number of elements that are
accessed sequentially. Lists may contain values of any X++ type. All the
values in the list must be of __the same__(this is the main difference
between lists and containers) type, given in the creation of the list. The
implementation of lists is such that traversal of the list elements is __very
fast.
Take a look for example at class Dialog addControl() method.
There controls are stored in ctrls List.
Map:
A map is a data type that associates one (key) value with another value [An
analog - a small table in memory with two fields: Keys, Values]. Both the key
and value values may be of any valid X++ type, including objects. The types
of the key and the value are given in the declaration of the map. The
implementation of maps is such that access to the values is _very fast_.
Don’t confuse map X++ types with Map objects in AOT, wich are used for
mapping tables with similar structures of fields
-
Set:
The functionality of Sets is similar with list.  A Set is just an unordered list of items, while a list of items held by a Map
are indexed via via a key.
Take look at
\Classes\sysLabel\LabelModuleId().

Get system Date and AX session Date

How to get session date time using preferred time zone in x ++
DateTimeUtil::date(DateTimeUtil::applyTimeZoneOffset(
DateTimeUtil::getSystemDateTime(), DateTimeUtil::getUserPreferredTimeZone())))


How to get system date in x++
systemDateGet();

Server and Client execution Process

What does server and client keywords do?
Many of the new x++ developers, when they start coding in x++ they really find hard to understand the usage of “Server” and “Client” keyword or server and client properties, Like when they should use the server method or the class and when the client keyword come into play. So I thought I should compose a post on this.
There are many books which throws light on this keyword, however they are more theoretical than practical and I think for a developer it is really necessary they are taught by live example by scenario so through the post below, I will try to be less theoretical and will try to be more practical and conceptual. Through my overall experience, I found that there are two ways to learn things

1. Fall and get stuck into the problem and try everything before you go through the book or search net (that who else fall into this problem and how he resolved it) and then use that solution. When you do that, you will understand the thinking behind the invention of the technique. You will get more clearer concept and you will have the Eagle’s eye view of the problems
2. Go through the book, copy paste the example and learn. But in this technique, it is hard to recall when you actually get into the problem.
Anyways let’s move to server client approach, I will try to keep it short and will post an example.
Ok so what does server keyword do?
Consider a scenario, where you as a developer or a technical consultant given a job to copy some files to the server through when someone setups something.
For instance, take an example of interest code in Account Receivable –> Collections –> Interest code. The scenario is, On the setting of interest code, you are setting something in file system, specific to the interest code and those file system settings will be used later by the interest calculation job for some calculation. Now, the requirement is there will be other web service that will be consuming those file settings.
What does the above scenario mean?
It means you need to do some setting in the code to copy the file system setting on the server as well. Now you if you see from top view, you need to do two things
1. Create Files on the client (Use the client class or client method for it)
2. Create files on the server because copy is not allowed between client and server normally.(Use the server keyword or server class for it)
How can I make server and client methods?
It is easy just put the server or client keyword before the method or class name and method should be static for Server. Client does not have this limitation.
We also have keyword Server Client for the method and class, what does it mean?
The server client method works as the Called from property of the AOT Object, which means the object execution will be dependent on the caller. If the client method or class is calling the server client class, the class or method will be executed on the client machine and vice versa.

Friday, November 4, 2011

AX2009 Send Mail using Mail Template in X++ code.

void krishh_UseMailTemplateAndSendMail(PurchTable _purchTable)
         {
             str                                 LIQ_message;
             Map                                 LIQ_emailParamMap;
             SysMailer     LIQ_Mailer;
       
             SysMailerAddressField               LIQ_tos;
             PurchtotalsForm                     LIQ_totalsForm;
             Amount                              LIQ_totalAmount;
             SysEmailId                           _emailId;
             SysEmailMessageTable                message;
             SysEmailTable                       Emailtable;
            
             ;
        
        
             //Get the Mail Template and Get the Message body

             _emailId = SysEmailTable::find(VendParameters::find().LIQ_DefaultMinPOTemplate).EmailId;

             Emailtable= SysEmailTable::find(_emailId);
             message=SysEmailMessageTable::find(_emailId,Emailtable.DefaultLanguage);
        
             LIQ_totalsForm   = PurchtotalsForm::newPurchTotalsForm(_purchTable, PurchUpdate::All);
             LIQ_totalsForm.calctotals();
             LIQ_totalAmount=LIQ_totalsForm.invoiceAmountValue();
        
            if (message.LayoutType == SysEmailLayoutType::StaticLayout)
            {
                     LIQ_message = message.Mail;
            }
           
            //Set the variable values for the email template.
            LIQ_emailParamMap    = new Map(Types::String,Types::String);
            LIQ_emailParamMap.insert('PurchId', _purchTable.PurchId);
            LIQ_emailParamMap.insert('Total amount',num2str(abs(LIQ_totalAmount), 0, 2, 1, 0));
            LIQ_emailParamMap.insert('currecncy',CompanyInfo::find().CurrencyCode);
            LIQ_emailParamMap.insert('PurchName',_purchTable.PurchName);
            LIQ_message=SysEmailMessage::stringExpand(LIQ_message,LIQ_emailParamMap);

//Get the Mail parameters ex:mailserver.
           if(!LIQ_mailInitialized)
           {
               this.LIQ_initMail();
           }
 //validate the Mail ID before sending the mail.
           if (LIQ_mailer != null && SysEmailDistributor::validateEmail(RCMStoreTable::find
                                                                                                       (_purchTable.InventSiteId).Email))
          {
        
          LIQ_mailer.fromAddress(SysEmailTable::find("POminvalue").SenderAddr, "@SYS115063");
          LIQ_mailer.subject('Minimum PO value');
          LIQ_tos=LIQ_mailer.tos();
          LIQ_tos.clear();
          LIQ_tos.appendAddress(RCMStoreTable::find(_purchTable.InventSiteId).Email);
          LIQ_mailer.priority(1);
          LIQ_mailer.htmlBody(strReplace(LIQ_message, '\r\n', '<br>'));
          LIQ_mailer.sendMail();
          }
}/Method Close

         void LIQ_initMail()
         {
             SysEmailParameters parameters = SysEmailParameters::find();
             InteropPermission interopPermission;
             ;
             //initialize mailer class
             interopPermission = new InteropPermission(InteropKind::ComInterop);
             interopPermission.assert();
             LIQ_mailer = new SysMailer();
             if(parameters.smtpRelayServerName)
             {
                 LIQ_mailer.SMTPRelayServer(parameters.smtpRelayServerName,
                 parameters.smtpPortNumber,
                 parameters.smtpUserName,
                 SysEmailParameters::password(),
                 parameters.ntlm);
             }
             else
             {
                 LIQ_mailer.SMTPRelayServer(parameters.smtpServerIPAddress,
                 parameters.smtpPortNumber,
                 parameters.smtpUserName,
                 SysEmailParameters::password(),
                 parameters.ntlm);
             }
             LIQ_mailInitialized = true;
        
         }

How to get the AIF XML for OutBound interface using X++

static void krish_OutboundXMLAifProcess(Args _args)
    {
        AIFQueryCriteria   QueryCriteria=AIFQueryCriteria::construct();
        AIFCriteriaElement   criteriaElement1,criteriaElement2;
         EDSACRMOrderReturnService   CRMOrderReturnService;
         AIFXml  aifXMl;
        ;
        CRMOrderReturnService=EDSACRMOrderReturnService::construct();
        criteriaElement1=AIFCriteriaElement::newCriteriaElement(tablestr(EDSACRMSyncLog),fieldstr
                                   (EDSACRMSyncLog,CRMOrderId),AIFCriteriaOperator::Equal,'10345');
        criteriaElement2=AIFCriteriaElement::newCriteriaElement(tablestr(EDSACRMSyncLog),fieldstr 
                                  (EDSACRMSyncLog,DocReturn),AIFCriteriaOperator::Equal,Enum2str
                                  (NoYes::No));
      QueryCriteria.addCriteriaElement(criteriaElement1);
      QueryCriteria.addCriteriaElement(criteriaElement2);

      aifXMl=CRMOrderReturnService.find(QueryCriteria).serialize();

    }

Tuesday, November 1, 2011

Call File Outbound AIF Service using Query in X++

The following code is used to execute the AIF File outbound  Service using Query  for AIF find Operation.
ex:= Sales Table-salesid,another field and call AIF find to execute query criteria.

static void krish_OutboundAifQueryProcess(Args _args)
    {
        AxdSendContext          axdSendContext      = AxdSendContext::construct();
        AifAction               aifAction ;
        AifConstraint           aifConstraint       = new AifConstraint();
        AifConstraintList       aifConstraintList   = new AifConstraintList();
        AifOutboundProcessingService AifOutboundProcessingService =
                                                  new AifOutboundProcessingService();
        AifGatewaySendService   AifGatewaySendService = new AifGatewaySendService();
         AifActionId                 actionId;
         AifEndpointList             endpointList;
         Query                               query;
         QueryBuildDataSource      queryBuildDataSource;
        ;

        query = new Query(queryStr(AIFCRMOrderReturn));

        queryBuildDataSource = query.dataSourceTable(tablenum(EDSACRMSyncLog));
        queryBuildDataSource.addRange(fieldnum(EDSACRMSyncLog,CRMOrderId)).
                                                                                    value(QueryValue('2500591'));
        queryBuildDataSource.addRange(fieldnum(EDSACRMSyncLog,DocReturn)).
                                                                             value(QueryValue(NoYes::No));

       aifAction = AifAction::find(AifSendService::getDefaultSendAction(classnum
               (EDSACRMOrderReturnService),AifSendActionType::SendByQuery)) ;

        axdSendContext.parmXMLDocPurpose(XMLDocPurpose::Original);
        axdSendContext.parmSecurity(false);

        aifConstraint.parmType(AifConstraintType::NoConstraint);
        aifConstraintList.addConstraint(aifConstraint) ;

        endPointList=AifSendService::getEligibleEndpoints(aifAction.ActionId,aifConstraintList);

        AifSendService::submitFromQuery(aifAction.ActionId, endpointList, query, AifSendMode::Sync);

        AifGatewaySendService.run();
        AifOutboundProcessingService.run();
    }

Tuesday, October 25, 2011

Convert str to Date while importing from XML into AIF

When we are importing the date from xml to AIF we cannot convert that string to date using str2date().
The following function is used to convert from str to date.

Date ConvertXMLStrToDate(str _xmlValue)
{
    System.DateTime                 netDttm;
    str strTransDateTime;
    utcdatetime                     TransDateTime;
    ;
    new  InteropPermission(InteropKind::CLRInterop).assert();
    netDttm = System.DateTime::Parse(_xmlValue);
    TransDateTime=Global::utcDateTime2SystemDateTime(netDttm);
    return dateTimeUtil::date(TransDateTime);
}

Call from job

static void krishh_ConvertXmlStrToDate(Args _args)
{
date datevar;
System.DateTime                 netDttm;
str strTransDateTime;
utcdatetime                     TransDateTime;
EDSA_CreateSalesOrder createsales;
;
createsales=new EDSA_CreateSalesOrder();
strTransDateTime='2011-08-25 00:00:00';
datevar=createsales.ConvertXMLStrToDate(strTransDateTime);
}

Get all Tables from dictionary using x++

static void Krishh_IterateTables(Args _args)
{
    TreeNode            node;
    TreeNode            childNode;
    TreeNodeIterator    nodeIt;
    FilePath            path;
    ;
    path        = @'\Data dictionary\Tables';
    node        = TreeNode::findNode(path);
    nodeIt      = node.AOTiterator();
    childNode   = nodeIt.next();
    while(childNode)
    {
        info( childNode.treeNodeName());
        childNode = nodeIt.next();
    }
}

Monday, October 24, 2011

Extract Message from Exception infolog and Log into the ExceptionTable

Gets the Message from the Exception and Store into table.

public void writeInfoLogData(str ExceptionLogged,InfologData _infologData,str _orderId)
{
       SysExceptionTable               exceptionTable;
    SysInfoLogEnumerator            infoLogEnum;
    SysInfologMessageStruct         infoMessageStruct;
    EDSACRMSyncExceptionLog  CRMSyncLog;
    str  ExceptionMessage;
    ;
    infoLogEnum = SysInfoLogEnumerator::newData(_infologData);
    while(infoLogEnum.moveNext())
    {
        //Extract the message from the string
        infoMessageStruct = SysInfologMessageStruct::construct(infoLogEnum.currentMessage());
        ExceptionMessage +="\n" + infoMessageStruct.message();
    }
    CRMSyncLog.clear();
    CRMSyncLog.initValue();
    CRMSyncLog.ExceptionId=ExceptionLogged;
    CRMSyncLog.ExceptionTxt=ExceptionMessage;
    CRMSyncLog.RefOrderId=_orderId;
    CRMSyncLog.insert();
}

Call from job

static void krishh_LoadMessageFromException(Args _args)
{
    SalesTable salesTable;
    InfologData         infoData;
    AifInfoLog          aifInfoLog;
    container           infologData;
    ;
        infologData = connull();
    try
    {
        aifInfoLog = new AifInfoLog();
        salesTable.clear();
        salesTable.insert();
    }
    catch(Exception::Error)
    {
        infologData = aifInfoLog.getInfoLogData();
        EDSA_CreateSalesOrder::writeInfoLogData('SalesTableCreation',infologData,'10334');
    }
}

Saturday, October 22, 2011

Find mandatory fields in the table

This job is used to find the mandatory fields in the table.

static void Krishh_findMandatory(Args _args)
{
    TableId   tableId = tableNum(SalesTable);
    DictTable dt;
    int       fieldCnt;
    int       i;
    DictField df;
    ;
    dt = new DictTable(tableId);
    fieldCnt = dt.fieldCnt();
    for(i = 1; i <= fieldCnt; i++)
    {
        df = new DictField(tableId, dt.fieldCnt2Id(i));
        if(df.mandatory())
        {
            info("Field '" + df.name() + "' is mandatory.");
        }
    }
}

Call AIF OutBound Service using X++

This job is used to read the sales order based on Sales id and loads into the outboundchannel location.

static void krish_OutboundAifProcess(Args _args)
    {
        AxdSendContext          axdSendContext      = AxdSendContext::construct();
        AifEntityKey            aifEntityKey        = AifEntityKey::construct();
        AIFQueryCriteria   QueryCriteria;
        AifAction               aifAction ;
        AifConstraint           aifConstraint       = new AifConstraint();
        AifConstraintList       aifConstraintList   = new AifConstraintList();
        SalesTable  salesTable;
        AifOutboundProcessingService AifOutboundProcessingService = new AifOutboundProcessingService();
        AifGatewaySendService   AifGatewaySendService = new AifGatewaySendService();
        ;
        aifAction = AifAction::find(AifSendService::getDefaultSendAction(classnum(AIFCRMOrderReturnService),AifSendActionType::SendByKey)) ;
        salesTable = SalesTable::find("10334");
        aifEntityKey.parmTableId(salesTable.TableId);
        aifEntityKey.parmRecId(salesTable.RecId);
        aifEntityKey.parmKeyDataMap(SysDictTable::getKeyData(salesTable));
        axdSendContext.parmXMLDocPurpose(XMLDocPurpose::Original);
        axdSendContext.parmSecurity(false);
        aifConstraint.parmType(AifConstraintType::NoConstraint);
        aifConstraintList.addConstraint(aifConstraint) ;
        AifSendService::submitDefault(classnum(AIFCRMOrderReturnService),
                                        aifEntityKey,
                                        aifConstraintList,
                                        AifSendMode::Sync,
                                        axdSendContext.pack());
        AifOutboundProcessingService.run();
        AifGatewaySendService.run();
    }

Friday, October 14, 2011

Call AIFService File Endpoint

 static void kris_AIFService(Args _args)
    {
        AifGatewayReceiveService    AifGatewayReceiveService;
        AifInboundProcessingService AifInboundProcessingService;
        ;
        AifGatewayReceiveService = new AifGatewayReceiveService();
        AifGatewayReceiveService.run();
        AifInboundProcessingService = new AifInboundProcessingService();
        AifInboundProcessingService.run();
    }

Replace ALL function

This function is used to replace the string in the entire string.

str replaceAll( str s, str findStr, str replStr )
{
    int pos = strscan( s, findStr, 1, strlen( s ) );
    ;
    while( pos > 0 )
    {
        s = strdel( s, pos, strlen( findStr ) );
        s = strins( s, replStr, pos );
        pos = strscan( s, findStr, pos + strlen( replStr ), strlen( s ) );
    }
     return s;
}

Monday, September 19, 2011

IS and AS Keywords Introduced in AX2012

In Microsoft Dynamics AX2012, the X++ language provides the as and is expression operators to control downcast assignments. Downcast assignments involve class or table inheritance.

Assignment statements that implicitly downcast can cause errors that are hard for the programmer to predict or diagnose.

 You can use the as keyword to make your downcasts explicit.
You can use the is keyword to test whether a downcast is valid at run time.
as Example
static void AsTestJob33(Args _args)
{
    
    BaseClass basec; 
    DerivedClass derivedc;

    BottomClass bottomc; ;

    derivedc = new DerivedClass();

    basec = derivedc;

    derivedc = basec as DerivedClass;

    bottomc = new BottomClass();
    bottomc = basec as DerivedClass;
}

is Example

static void IsKeywordJob46(Args _args) 
{
    DerivedClass derivedc;
    BaseClass basec;

    basec = new DerivedClass();  // An upcast.
    if (basec IS DerivedClass)
    {
        info("Test 1: (basec IS DerivedClass) is true. Good.");
        derivedc = basec AS DerivedClass;
    }

    basec = new BaseClass();
    if (!(basec IS DerivedClass))
    {
        info("Test 2: (!(basec IS DerivedClass) is true. Good."));
    }
}

AX 2012 TempDB Tables

In Microsoft Dynamics AX, one type of temporary table is a TempDB table.

We call them TempDB tables because their TableType property value is TempDB. This value comes from the TableType::TempDB enum value. The TableType property value can be set at AOT > Data Dictionary > Tables > MyTempDBTable > Properties > TableType.

Only processes that run on the server tier can instantiate TempDB tables.

Following Sample will give the little picture about how to use the TempDb Tables

Create the Class with this main method and Run the class
server public static void main(Args _args)
{
    MyTempdb MyTempdb;
    CustTable custTable;
    TableType tableTypeEnum;
    str stringWork;
    ;
   
    info("Start of main.");
    MyTempdb;.AccountNum = "4004";
    MyTempdb;.doInsert();
    MyTempdb;.AccountNum = "4005";
    MyTempdb;.doInsert();
    tableTypeEnum = MyTempdb;.getTableType();
    stringWork = "MyTempdb.TableType is: " + enum2Str(tableTypeEnum);
    info(stringWork);
    while select *
            from custTable
                JOIN MyTempdb;
            where
                MyTempdb;.AccountNum == custTable.AccountNum
    {
        stringWork = custTable.AccountNum
                + " , "
                + int2Str(custTable.MandatoryCreditLimit);
        info(stringWork);
    }
}

BP Deviation Update for AOT while Upgrading

Following job is used to update the methods with the BP Deviation documented while upgrading from one version to another version.
This job is used to update for tables,Classes,Views,FormMethods,ReportMethods.

This wont update at the datasource for forms or reports level.

static void krishh_AddBPDeviationDocumented(Args _args)
{
    TreeNode            node;

    TreeNode            childNode;
    TreeNodeIterator    nodeIt;
    FilePath            path;
    str filter,filter1;
    str BPFix='//BP Deviation Documented';
    str firstLine;
    List listSource=new List(Types::String);
    ListIterator listIterator;
    str BPFixFind='//BP Deviation Documented*';

    ;

    filter='*display*';
    filter1='*edit*';

    path        = @'\Data dictionary\tables\CustTable';
   // path        = @'\Data dictionary\views\CustTable';
    //path=@'\Classes';
    //path=@\Forms';
    path += '\\Methods';

    node        = TreeNode::findNode(path);
    nodeIt      = node.AOTiterator();

    childNode   = nodeIt.next();
    if(childNode.AOTLayer()==UtilEntryLevel::var)
    {
        while(childNode)
        {
            listSource=Global::strSplit(childNode.AOTgetSource(),'()');
            listIterator=new listIterator(listSource);
            if(listIterator.more())
            {
               if(firstLine like BPFixFind)
               {
                   info(strFmt('Table.Method:%1.%2 is already BP Deviated',childNode.AOTparent(),childNode.AOTname()));
               }
               else
                {
                  if((firstLine like filter) || (firstLine like filter1))
                    {
                        childNode.AOTsetSource(BPFix+"\n"+ childNode.AOTgetSource());
                        childNode.AOTcompile();
                        childNode.AOTsave();
                    }
                }
            }
            childNode = nodeIt.next();
        }
    }
}

Excute Query using X++

static void krishh_RunQueryFormusingCode(Args _args)
{
    QueryRun    SalesQuotationUpdate;
    SalesQuotationTable SalesQuotationTable;
    ;
    SalesQuotationUpdate = new QueryRun(QueryStr(SalesQuotationUpdate));
    if (SalesQuotationUpdate.prompt())
    {
        while (SalesQuotationUpdate.next())
        {
            if (SalesQuotationUpdate.changed(tablenum(SalesQuotationTable)))
            {
                SalesQuotationTable = SalesQuotationUpdate.get(
                    tablenum(SalesQuotationTable));
                info(SalesQuotationTable.QuotationId);
            }
        }
    }
}

Expressions in QueryRanges

static void krishh_ExpressionInQuery(Args _args)
{
    Query q = new Query(); 
    QueryRun qr;
    CustTable ct;
    QueryBuildDataSource qbr1;
    ;
    qbr1 = q.addDataSource(tablenum(CustTable));
    qbr1.name("Customer");
    qbr1.addRange(fieldNum(CustTable, AccountNum)).value(
    strFmt('((%1.%2 == "4000") || (%1.%3 == "The Bulb"))',
        qbr1.name(),
        fieldStr(CustTable, AccountNum),
        fieldStr(CustTable, Name)));
       info(qbr1.toString());
    qr = new QueryRun(q);
    while (qr.next())
    {
        if (qr.changedNo(1))
        {
            ct = qr.getNo(1);
            info(strfmt('CustomerAccountNum=%1 And Customer Name=%2',ct.AccountNum,ct.Name));
        }
    }
}

Friday, September 16, 2011

AX2012 QueryFilter Class

The following X++ code example uses the QueryFilter class to filter the result set from an outer join.

The QrySalesAndLines query joins the parent and child tables on an equality test between parent's primary key field and child's foreign key field. The following job adds another join filter criteria which tests whether the Quantity field equals 66.

static void krishh_QueryFilter(Args _args)
{
    Query query2;
    QueryBuildDataSource qbDataSource3;
    QueryRun queryRun4;
    // Ax2012 class
    QueryFilter qFilter7;
    SalesOrder recSalesOrder;
    SalesOrderLine recSalesOrderLine;
    struct struct5;
    struct5 = new struct
        ("int SalesOrderID;"
        + "date DateAdded;"
        + "str SalesOrderLineID;"
        + "int Quantity"
        );
    query2 = new Query("QrySalesAndLines");
    qbDataSource3 = query2.dataSourceName("SalesOrderLine_1");
    qFilter7 = query2.addQueryFilter(qbDataSource3, "Quantity");
    qFilter7.value("66");
    queryRun4 = new QueryRun(query2);
    while (queryRun4.next())
    {
        recSalesOrder = queryRun4.getNo(1);
        recSalesOrderLine = queryRun4.getNo(2);
        struct5.value("SalesOrderID", recSalesOrder.SalesOrderID);
        struct5.value("DateAdded", recSalesOrder.DateAdded);
        struct5.value("SalesOrderLineID", recSalesOrderLine.SalesOrderLineID);
        struct5.value("Quantity", recSalesOrderLine.Quantity);
        info(struct5.toString());
    }
}

Execute External DB Query using X++


static void krishh_ExecuteExternalDBQuery()
{
    LoginProperty loginProperty;
    OdbcConnection odbcConnection;
    Statement statement;
    ResultSet resultSet;
    str sql, criteria;
    SqlStatementExecutePermission perm;
    ;
    // Set the information on the ODBC.
    loginProperty = new LoginProperty();
    loginProperty.setDSN("dsnName");
    loginProperty.setDatabase("databaseName");
    //Create a connection to external database.
    odbcConnection = new OdbcConnection(loginProperty);
    if (odbcConnection)
    {
        sql = "SELECT * FROM MYTABLE WHERE FIELD = "
            + criteria
            + " ORDER BY FIELD1, FIELD2 ASC ;";
        //Assert permission for executing the sql string.
        perm = new SqlStatementExecutePermission(sql);
        perm.assert();
        //Prepare the sql statement.
        statement = odbcConnection.createStatement();
        resultSet = statement.executeQuery(sql);
        //Cause the sql statement to run,
        //then loop through each row in the result.
        while (resultSet.next())
        {
            //It is not possible to get field 3 and then 1.
            //Always get fields in numerical order, such as 1 then 2 the 3 etc.
            print resultSet.getString(1);
            print resultSet.getString(3);
        }
        //Close the connection.
        resultSet.close();
        statement.close();
    }
    else
    {
        error("Failed to log on to the database through ODBC.");
    }
}

ADD Query to AOT using X++

Sample X++ code used to Add the Query to AOT.
static void Krishh_addQueryToAOTUsingX++(Args _args)
{
    TreeNode                treeNode;
    Query                   query;
    QueryBuildDataSource    qbds;
    QueryBuildRange         qbr;
    str                     queryName = "MyQuery";
    #AOT
    ;
    treeNode = TreeNode::findNode(#QueriesPath);
    query = treeNode.AOTfindChild(queryName);
    if (!query)
    {
        treeNode.AOTadd(queryName);
        query = treeNode.AOTfindChild(queryName);
        qbds  = query.addDataSource(tablenum(Address));
        qbr   = qbds.addRange(fieldnum(Address,Name));
        query.AOTcompile(1);
        query.AOTsave();
    }
}

Tuesday, September 13, 2011

AX2012 Compiler Window Changes Explained

Following diagaram explains the Compiler window changes.

Ax2012 Meta Model

What is new in the meta model for AX 2012? Quite a lot – here is a short summary:
  • SSRS Report is added, it will replace Reporting Library and Report.
    Create an SSRS Report for any reporting needs.
  • Parts are introduced: Info Part, Form Part, Cue and Cue Group.
    These all provides additional information that can be hosted on a form. An Info Part provides a simple grid, a Form Part can host a form inside a form, a Cue provides visual KPIs.
  • Service and Service Group Provides capabilities for exposing X++ classes as .NET services – wsdl style.
  • New security concepts: Role, Duty, Privilege, Security Policy, Code Permission and Process Cycle. These are all part of the new role based security framework. Notice the meta model contains Role – allowing the developer to specify predefined roles.
  • Security key is deprecated
  • Visual Studio Project is added
    You can now have VS projects (like C# and VB) in the meta model – yes, they support layer based customization and rapid deployment like all other meta model concepts.
  • …. and much more
 The diagram below shows the most common relationships between concepts. Each arrow should be read as “using”. For example:
  • Menu uses Menu Item. A menu has a collection menu items.
  • Menu Item uses Form. A menu item references a form. This form is launched when the user clicks the menu item.
  • Form uses Table. A form uses a table as a data source.
  • Table uses Extended Data Type. The fields on a table is defined using extended data types. 
Provided the below diagram, and pinned it to a wall in my office. It serves me as a reference when I navigate the new areas of AX 2012. I hope it can do the same for you.

Changes to the Collection Classes in Dynamics AX 2012

In Microsoft Dynamics AX 2012 we introduced the ability to compile X++ code into .NET Framework CIL.
We intend for X++ code to produce the same results whether it is interpreted or run as CIL. We therefore had to make some minor changes to legacy X++ behavior.

This post explains the change to the legacy behavior of the X++ interpreter in the area of the AX collection classes, which include the Map, Set, List, and Array classes.

1. Type Checking to Ensure Match

The first X++ interpreter change is an increase in the type matching checks for the AX collection classes.  In most cases, the value inserted into a collection should have the same or compatible type as the defined type of the collection classes.

For example-
Define a list with type being string, but try to insert a different type:

    List l = new List(Types::String);
        l.addFront(1);

 Here an exception will be raised and the following text will be shown in the info log

The expected type was str, but the encountered type was int.
Ax2012 introduced this new restriction in X++ because type mismatches are not allowed in CIL.

NOTE: Due to legacy code issues of type mismatching in Maps, this check has been temporarily disabled for Maps in the X++ interpreter. This type check is enabled for the other collections (namely Set, List, and Array).

2. Disallow Null Values

The second change is that null values are no longer allowed as elements in Sets, or as keys in Maps.  Sets and Maps in .NET Framework have this same restriction. 

For example:
static void krishh_NullValidation(Args _args)
{
 
   Map m;
 
   Set s;
 
   Query q;

     ;
 
   m = new Map(Types::Class,Types::String);
 
   m.insert(q, "abc");  //q is null which is not instantiated so an exception is raised.

 
   s = new Set(Types::Class);
 
   s.add(q);  //q is null so an exception is raised.
 }

3. Invalidate Enumerators/Iterators After Changing Elements
The third change is to invalidate enumerators and iterators of a Map or Set object if any element of the collection is added or removed after the enumerator/iterator is created. 

Consider the following code, where a map is initialized with a few elements.
static void Job2(Args _args)
{
    Map m;
    MapIterator it;
    str s;
 
    m = new Map(types::Integer,Types::String);
    it = new MapIterator(m);  //The map iterator is constructed.

    m.insert(1, "abc");
    m.insert(2, "def");
    m.insert(4, "ghi");
    m.remove(2);  //Contents of map are modified.
 
   it.begin(); 
//This access of the iterator raises an exception.


 
}

An exception will be raised with the following message:
The iterator does not designate a valid element.

A map can be modified by MapIterator.delete() as well, and this will also cause the iterator to be invalid after the delete(). 

For example:
static void Job3(Args _args)
{
    Map m;
    MapIterator it;
 
    m = new Map(types::Integer,Types::String);
          it = new MapIterator(m);  //The map iterator is constructed.
 
   m.insert(1, "abc");
    m.insert(2, "def");
    m.insert(4, "ghi");
         it.begin();         info(it.toString());
  
   it.delete();  //An element is deleted from the map.
    
   it.next();    
//The iterator is no longer valid, so an exception is raised.
 
      
info(it.toString());


 
}

How shall we handle legacy X++ code that removes elements from a Map using MapIterator?

One option is to iterate through the Map elements, and copy the key of each unwanted element into a Set.  Next iterate through the Set, and for element, delete its match from the Map.

For example:
static void Job4(Args _args)
{
    Map m;
    MapIterator it;
    Set s;
 
   SetEnumerator sm;

 
   m = new Map(types::Integer,Types::String); 
//Keys are of type Integer.
 
   m.insert(1, "abc");
    m.insert(2, "def");
    m.insert(4, "ghi");
 
   it = new MapIterator(m);
 
   s = new Set(Types::Integer); //This Set stores the type that matches the Map keys.
 
   it.begin();
 
   while(it.more()) //Iterate through the Map keys.
 
   {
 
       if(it.domainValue() mod 2 == 0)
 
           s.add(it.domainValue());  //Copy this unwanted key into the Set. 
 
       it.next();
 
   }
 

 
   sm = s.getEnumerator();
 
   while(sm.moveNext()) //Iterate through the Set.
 
   {
        m.remove(sm.current());  //Delete the key from the Map.

 
 }

}


Remaining Inconsistencies
Currently there remain two inconsistencies in AX collection behavior between (a) X++ by the interpreter versus (b) X++ as CIL. One involves deletions, the other insertions:

  1. Deletions:  Deleting from a Set by using SetIterator.delete() works fine when running X++ by the interpreter. Yet in X++ as CIL this deletion raises an exception saying the iterator is invalid.  We will fix this inconsistency in the near future.
  2. Insertions:  Inserting an item into a Map or Set that is currently being used by an enumerator or iterator works fine when running X++ by the interpreter. Yet in X++ as CIL the enumerator or iterator becomes invalid after the insertion and it raises an exception.
For #2 Insertions into a Map, the inconsistency is illustrated in the following example:
static void Job5(Args _args)
{
    Map m = new Map(types::String,Types::Integer);
    MapEnumerator me =  m.GetEnumerator();
     m.insert("1", 10);          info(m.toString());
   
  me.moveNext(); //This line causes different behavior between X++ interpreter versus CIL.
 
   info(me.toString());


This job run successfully in the interpreter gives results.

However, if we run the above job as CIL, then an exception is raised:
System.InvalidOperationException: Collection was modified after the enumerator was instantiated.
}